aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide')
-rw-r--r--crates/ra_ide/Cargo.toml2
-rw-r--r--crates/ra_ide/src/assists.rs7
-rw-r--r--crates/ra_ide/src/change.rs61
-rw-r--r--crates/ra_ide/src/expand.rs8
-rw-r--r--crates/ra_ide/src/imports_locator.rs76
-rw-r--r--crates/ra_ide/src/lib.rs4
-rw-r--r--crates/ra_ide/src/references.rs138
-rw-r--r--crates/ra_ide/src/runnables.rs16
-rw-r--r--crates/ra_ide/src/snapshots/highlighting.html10
-rw-r--r--crates/ra_ide/src/snapshots/rainbow_highlighting.html12
-rw-r--r--crates/ra_ide/src/syntax_highlighting.rs289
11 files changed, 465 insertions, 158 deletions
diff --git a/crates/ra_ide/Cargo.toml b/crates/ra_ide/Cargo.toml
index 2c9f9dce0..53817d1f7 100644
--- a/crates/ra_ide/Cargo.toml
+++ b/crates/ra_ide/Cargo.toml
@@ -39,7 +39,7 @@ ra_assists = { path = "../ra_assists" }
39hir = { path = "../ra_hir", package = "ra_hir" } 39hir = { path = "../ra_hir", package = "ra_hir" }
40 40
41[dev-dependencies] 41[dev-dependencies]
42insta = "0.12.0" 42insta = "0.13.0"
43 43
44[dev-dependencies.proptest] 44[dev-dependencies.proptest]
45version = "0.9.0" 45version = "0.9.0"
diff --git a/crates/ra_ide/src/assists.rs b/crates/ra_ide/src/assists.rs
index a936900da..c43c45c65 100644
--- a/crates/ra_ide/src/assists.rs
+++ b/crates/ra_ide/src/assists.rs
@@ -2,8 +2,9 @@
2 2
3use ra_db::{FilePosition, FileRange}; 3use ra_db::{FilePosition, FileRange};
4 4
5use crate::{db::RootDatabase, FileId, SourceChange, SourceFileEdit}; 5use crate::{
6 6 db::RootDatabase, imports_locator::ImportsLocatorIde, FileId, SourceChange, SourceFileEdit,
7};
7use either::Either; 8use either::Either;
8pub use ra_assists::AssistId; 9pub use ra_assists::AssistId;
9use ra_assists::{AssistAction, AssistLabel}; 10use ra_assists::{AssistAction, AssistLabel};
@@ -16,7 +17,7 @@ pub struct Assist {
16} 17}
17 18
18pub(crate) fn assists(db: &RootDatabase, frange: FileRange) -> Vec<Assist> { 19pub(crate) fn assists(db: &RootDatabase, frange: FileRange) -> Vec<Assist> {
19 ra_assists::assists(db, frange) 20 ra_assists::assists_with_imports_locator(db, frange, ImportsLocatorIde::new(db))
20 .into_iter() 21 .into_iter()
21 .map(|assist| { 22 .map(|assist| {
22 let file_id = frange.file_id; 23 let file_id = frange.file_id;
diff --git a/crates/ra_ide/src/change.rs b/crates/ra_ide/src/change.rs
index b0aa2c8e0..45a58690b 100644
--- a/crates/ra_ide/src/change.rs
+++ b/crates/ra_ide/src/change.rs
@@ -166,13 +166,15 @@ impl LibraryData {
166const GC_COOLDOWN: time::Duration = time::Duration::from_millis(100); 166const GC_COOLDOWN: time::Duration = time::Duration::from_millis(100);
167 167
168impl RootDatabase { 168impl RootDatabase {
169 pub(crate) fn request_cancellation(&mut self) {
170 let _p = profile("RootDatabase::request_cancellation");
171 self.salsa_runtime_mut().synthetic_write(Durability::LOW);
172 }
173
169 pub(crate) fn apply_change(&mut self, change: AnalysisChange) { 174 pub(crate) fn apply_change(&mut self, change: AnalysisChange) {
170 let _p = profile("RootDatabase::apply_change"); 175 let _p = profile("RootDatabase::apply_change");
176 self.request_cancellation();
171 log::info!("apply_change {:?}", change); 177 log::info!("apply_change {:?}", change);
172 {
173 let _p = profile("RootDatabase::apply_change/cancellation");
174 self.salsa_runtime_mut().synthetic_write(Durability::LOW);
175 }
176 if !change.new_roots.is_empty() { 178 if !change.new_roots.is_empty() {
177 let mut local_roots = Vec::clone(&self.local_roots()); 179 let mut local_roots = Vec::clone(&self.local_roots());
178 for (root_id, is_local) in change.new_roots { 180 for (root_id, is_local) in change.new_roots {
@@ -299,45 +301,74 @@ impl RootDatabase {
299 )*} 301 )*}
300 } 302 }
301 sweep_each_query![ 303 sweep_each_query![
304 // SourceDatabase
302 ra_db::ParseQuery 305 ra_db::ParseQuery
303 ra_db::SourceRootCratesQuery 306 ra_db::SourceRootCratesQuery
307
308 // AstDatabase
304 hir::db::AstIdMapQuery 309 hir::db::AstIdMapQuery
305 hir::db::ParseMacroQuery 310 hir::db::InternMacroQuery
306 hir::db::MacroDefQuery
307 hir::db::MacroArgQuery 311 hir::db::MacroArgQuery
312 hir::db::MacroDefQuery
313 hir::db::ParseMacroQuery
308 hir::db::MacroExpandQuery 314 hir::db::MacroExpandQuery
315
316 // DefDatabase
317 hir::db::RawItemsQuery
318 hir::db::ComputeCrateDefMapQuery
309 hir::db::StructDataQuery 319 hir::db::StructDataQuery
320 hir::db::UnionDataQuery
310 hir::db::EnumDataQuery 321 hir::db::EnumDataQuery
322 hir::db::ImplDataQuery
311 hir::db::TraitDataQuery 323 hir::db::TraitDataQuery
312 hir::db::RawItemsQuery
313 hir::db::ComputeCrateDefMapQuery
314 hir::db::GenericParamsQuery
315 hir::db::FunctionDataQuery
316 hir::db::TypeAliasDataQuery 324 hir::db::TypeAliasDataQuery
325 hir::db::FunctionDataQuery
317 hir::db::ConstDataQuery 326 hir::db::ConstDataQuery
318 hir::db::StaticDataQuery 327 hir::db::StaticDataQuery
328 hir::db::BodyWithSourceMapQuery
329 hir::db::BodyQuery
330 hir::db::ExprScopesQuery
331 hir::db::GenericParamsQuery
332 hir::db::AttrsQuery
319 hir::db::ModuleLangItemsQuery 333 hir::db::ModuleLangItemsQuery
320 hir::db::CrateLangItemsQuery 334 hir::db::CrateLangItemsQuery
321 hir::db::LangItemQuery 335 hir::db::LangItemQuery
322 hir::db::DocumentationQuery 336 hir::db::DocumentationQuery
323 hir::db::ExprScopesQuery 337
338 // InternDatabase
339 hir::db::InternFunctionQuery
340 hir::db::InternStructQuery
341 hir::db::InternUnionQuery
342 hir::db::InternEnumQuery
343 hir::db::InternConstQuery
344 hir::db::InternStaticQuery
345 hir::db::InternTraitQuery
346 hir::db::InternTypeAliasQuery
347 hir::db::InternImplQuery
348
349 // HirDatabase
324 hir::db::DoInferQuery 350 hir::db::DoInferQuery
325 hir::db::TyQuery 351 hir::db::TyQuery
326 hir::db::ValueTyQuery 352 hir::db::ValueTyQuery
353 hir::db::ImplSelfTyQuery
354 hir::db::ImplTraitQuery
327 hir::db::FieldTypesQuery 355 hir::db::FieldTypesQuery
328 hir::db::CallableItemSignatureQuery 356 hir::db::CallableItemSignatureQuery
357 hir::db::GenericPredicatesForParamQuery
329 hir::db::GenericPredicatesQuery 358 hir::db::GenericPredicatesQuery
330 hir::db::GenericDefaultsQuery 359 hir::db::GenericDefaultsQuery
331 hir::db::BodyWithSourceMapQuery
332 hir::db::BodyQuery
333 hir::db::ImplsInCrateQuery 360 hir::db::ImplsInCrateQuery
334 hir::db::ImplsForTraitQuery 361 hir::db::ImplsForTraitQuery
362 hir::db::TraitSolverQuery
363 hir::db::InternTypeCtorQuery
364 hir::db::InternChalkImplQuery
365 hir::db::InternAssocTyValueQuery
335 hir::db::AssociatedTyDataQuery 366 hir::db::AssociatedTyDataQuery
367 hir::db::AssociatedTyValueQuery
368 hir::db::TraitSolveQuery
336 hir::db::TraitDatumQuery 369 hir::db::TraitDatumQuery
337 hir::db::StructDatumQuery 370 hir::db::StructDatumQuery
338 hir::db::ImplDatumQuery 371 hir::db::ImplDatumQuery
339 hir::db::ImplDataQuery
340 hir::db::TraitSolveQuery
341 ]; 372 ];
342 acc.sort_by_key(|it| std::cmp::Reverse(it.1)); 373 acc.sort_by_key(|it| std::cmp::Reverse(it.1));
343 acc 374 acc
diff --git a/crates/ra_ide/src/expand.rs b/crates/ra_ide/src/expand.rs
index b82259a3d..831438c09 100644
--- a/crates/ra_ide/src/expand.rs
+++ b/crates/ra_ide/src/expand.rs
@@ -79,6 +79,14 @@ pub(crate) fn descend_into_macros(
79 let source_analyzer = 79 let source_analyzer =
80 hir::SourceAnalyzer::new(db, src.with_value(src.value.parent()).as_ref(), None); 80 hir::SourceAnalyzer::new(db, src.with_value(src.value.parent()).as_ref(), None);
81 81
82 descend_into_macros_with_analyzer(db, &source_analyzer, src)
83}
84
85pub(crate) fn descend_into_macros_with_analyzer(
86 db: &RootDatabase,
87 source_analyzer: &hir::SourceAnalyzer,
88 src: InFile<SyntaxToken>,
89) -> InFile<SyntaxToken> {
82 successors(Some(src), |token| { 90 successors(Some(src), |token| {
83 let macro_call = token.value.ancestors().find_map(ast::MacroCall::cast)?; 91 let macro_call = token.value.ancestors().find_map(ast::MacroCall::cast)?;
84 let tt = macro_call.token_tree()?; 92 let tt = macro_call.token_tree()?;
diff --git a/crates/ra_ide/src/imports_locator.rs b/crates/ra_ide/src/imports_locator.rs
new file mode 100644
index 000000000..48b014c7d
--- /dev/null
+++ b/crates/ra_ide/src/imports_locator.rs
@@ -0,0 +1,76 @@
1//! This module contains an import search funcionality that is provided to the ra_assists module.
2//! Later, this should be moved away to a separate crate that is accessible from the ra_assists module.
3
4use crate::{
5 db::RootDatabase,
6 references::{classify_name, NameDefinition, NameKind},
7 symbol_index::{self, FileSymbol},
8 Query,
9};
10use hir::{db::HirDatabase, ModuleDef, SourceBinder};
11use ra_assists::ImportsLocator;
12use ra_prof::profile;
13use ra_syntax::{ast, AstNode, SyntaxKind::NAME};
14
15pub(crate) struct ImportsLocatorIde<'a> {
16 source_binder: SourceBinder<'a, RootDatabase>,
17}
18
19impl<'a> ImportsLocatorIde<'a> {
20 pub(crate) fn new(db: &'a RootDatabase) -> Self {
21 Self { source_binder: SourceBinder::new(db) }
22 }
23
24 fn get_name_definition(
25 &mut self,
26 db: &impl HirDatabase,
27 import_candidate: &FileSymbol,
28 ) -> Option<NameDefinition> {
29 let _p = profile("get_name_definition");
30 let file_id = import_candidate.file_id.into();
31 let candidate_node = import_candidate.ptr.to_node(&db.parse_or_expand(file_id)?);
32 let candidate_name_node = if candidate_node.kind() != NAME {
33 candidate_node.children().find(|it| it.kind() == NAME)?
34 } else {
35 candidate_node
36 };
37 classify_name(
38 &mut self.source_binder,
39 hir::InFile { file_id, value: &ast::Name::cast(candidate_name_node)? },
40 )
41 }
42}
43
44impl ImportsLocator for ImportsLocatorIde<'_> {
45 fn find_imports(&mut self, name_to_import: &str) -> Vec<ModuleDef> {
46 let _p = profile("search_for_imports");
47 let db = self.source_binder.db;
48
49 let project_results = {
50 let mut query = Query::new(name_to_import.to_string());
51 query.exact();
52 query.limit(40);
53 symbol_index::world_symbols(db, query)
54 };
55 let lib_results = {
56 let mut query = Query::new(name_to_import.to_string());
57 query.libs();
58 query.exact();
59 query.limit(40);
60 symbol_index::world_symbols(db, query)
61 };
62
63 project_results
64 .into_iter()
65 .chain(lib_results.into_iter())
66 .filter_map(|import_candidate| self.get_name_definition(db, &import_candidate))
67 .filter_map(|name_definition_to_import| {
68 if let NameKind::Def(module_def) = name_definition_to_import.kind {
69 Some(module_def)
70 } else {
71 None
72 }
73 })
74 .collect()
75 }
76}
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs
index 4d8deb21c..03ad6b2c1 100644
--- a/crates/ra_ide/src/lib.rs
+++ b/crates/ra_ide/src/lib.rs
@@ -30,6 +30,7 @@ mod syntax_highlighting;
30mod parent_module; 30mod parent_module;
31mod references; 31mod references;
32mod impls; 32mod impls;
33mod imports_locator;
33mod assists; 34mod assists;
34mod diagnostics; 35mod diagnostics;
35mod syntax_tree; 36mod syntax_tree;
@@ -202,6 +203,9 @@ impl AnalysisHost {
202 pub fn per_query_memory_usage(&mut self) -> Vec<(String, ra_prof::Bytes)> { 203 pub fn per_query_memory_usage(&mut self) -> Vec<(String, ra_prof::Bytes)> {
203 self.db.per_query_memory_usage() 204 self.db.per_query_memory_usage()
204 } 205 }
206 pub fn request_cancellation(&mut self) {
207 self.db.request_cancellation();
208 }
205 pub fn raw_database( 209 pub fn raw_database(
206 &self, 210 &self,
207 ) -> &(impl hir::db::HirDatabase + salsa::Database + ra_db::SourceDatabaseExt) { 211 ) -> &(impl hir::db::HirDatabase + salsa::Database + ra_db::SourceDatabaseExt) {
diff --git a/crates/ra_ide/src/references.rs b/crates/ra_ide/src/references.rs
index 5e2fe1905..ebded715d 100644
--- a/crates/ra_ide/src/references.rs
+++ b/crates/ra_ide/src/references.rs
@@ -112,25 +112,20 @@ impl IntoIterator for ReferenceSearchResult {
112 112
113pub(crate) fn find_all_refs( 113pub(crate) fn find_all_refs(
114 db: &RootDatabase, 114 db: &RootDatabase,
115 mut position: FilePosition, 115 position: FilePosition,
116 search_scope: Option<SearchScope>, 116 search_scope: Option<SearchScope>,
117) -> Option<RangeInfo<ReferenceSearchResult>> { 117) -> Option<RangeInfo<ReferenceSearchResult>> {
118 let parse = db.parse(position.file_id); 118 let parse = db.parse(position.file_id);
119 let syntax = parse.tree().syntax().clone(); 119 let syntax = parse.tree().syntax().clone();
120 120
121 let token = syntax.token_at_offset(position.offset); 121 let (opt_name, search_kind) =
122 let mut search_kind = ReferenceKind::Other; 122 if let Some(name) = get_struct_def_name_for_struc_litetal_search(&syntax, position) {
123 (Some(name), ReferenceKind::StructLiteral)
124 } else {
125 (find_node_at_offset::<ast::Name>(&syntax, position.offset), ReferenceKind::Other)
126 };
123 127
124 if let TokenAtOffset::Between(ref left, ref right) = token { 128 let RangeInfo { range, info: (name, def) } = find_name(db, &syntax, position, opt_name)?;
125 if (right.kind() == SyntaxKind::L_CURLY || right.kind() == SyntaxKind::L_PAREN)
126 && left.kind() != SyntaxKind::IDENT
127 {
128 position = FilePosition { offset: left.text_range().start(), ..position };
129 search_kind = ReferenceKind::StructLiteral;
130 }
131 }
132
133 let RangeInfo { range, info: (name, def) } = find_name(db, &syntax, position)?;
134 129
135 let declaration = match def.kind { 130 let declaration = match def.kind {
136 NameKind::Macro(mac) => mac.to_nav(db), 131 NameKind::Macro(mac) => mac.to_nav(db),
@@ -170,9 +165,10 @@ fn find_name(
170 db: &RootDatabase, 165 db: &RootDatabase,
171 syntax: &SyntaxNode, 166 syntax: &SyntaxNode,
172 position: FilePosition, 167 position: FilePosition,
168 opt_name: Option<ast::Name>,
173) -> Option<RangeInfo<(String, NameDefinition)>> { 169) -> Option<RangeInfo<(String, NameDefinition)>> {
174 let mut sb = SourceBinder::new(db); 170 let mut sb = SourceBinder::new(db);
175 if let Some(name) = find_node_at_offset::<ast::Name>(&syntax, position.offset) { 171 if let Some(name) = opt_name {
176 let def = classify_name(&mut sb, InFile::new(position.file_id.into(), &name))?; 172 let def = classify_name(&mut sb, InFile::new(position.file_id.into(), &name))?;
177 let range = name.syntax().text_range(); 173 let range = name.syntax().text_range();
178 return Some(RangeInfo::new(range, (name.text().to_string(), def))); 174 return Some(RangeInfo::new(range, (name.text().to_string(), def)));
@@ -218,15 +214,8 @@ fn process_definition(
218 if let Some(d) = classify_name_ref(&mut sb, InFile::new(file_id.into(), &name_ref)) 214 if let Some(d) = classify_name_ref(&mut sb, InFile::new(file_id.into(), &name_ref))
219 { 215 {
220 if d == def { 216 if d == def {
221 let kind = if name_ref 217 let kind = if is_record_lit_name_ref(&name_ref)
222 .syntax() 218 || is_call_expr_name_ref(&name_ref)
223 .ancestors()
224 .find_map(ast::RecordLit::cast)
225 .and_then(|l| l.path())
226 .and_then(|p| p.segment())
227 .and_then(|p| p.name_ref())
228 .map(|n| n == name_ref)
229 .unwrap_or(false)
230 { 219 {
231 ReferenceKind::StructLiteral 220 ReferenceKind::StructLiteral
232 } else { 221 } else {
@@ -301,6 +290,49 @@ fn reference_access(kind: &NameKind, name_ref: &ast::NameRef) -> Option<Referenc
301 mode.or(Some(ReferenceAccess::Read)) 290 mode.or(Some(ReferenceAccess::Read))
302} 291}
303 292
293fn is_record_lit_name_ref(name_ref: &ast::NameRef) -> bool {
294 name_ref
295 .syntax()
296 .ancestors()
297 .find_map(ast::RecordLit::cast)
298 .and_then(|l| l.path())
299 .and_then(|p| p.segment())
300 .map(|p| p.name_ref().as_ref() == Some(name_ref))
301 .unwrap_or(false)
302}
303
304fn get_struct_def_name_for_struc_litetal_search(
305 syntax: &SyntaxNode,
306 position: FilePosition,
307) -> Option<ast::Name> {
308 if let TokenAtOffset::Between(ref left, ref right) = syntax.token_at_offset(position.offset) {
309 if right.kind() != SyntaxKind::L_CURLY && right.kind() != SyntaxKind::L_PAREN {
310 return None;
311 }
312 if let Some(name) = find_node_at_offset::<ast::Name>(&syntax, left.text_range().start()) {
313 return name.syntax().ancestors().find_map(ast::StructDef::cast).and_then(|l| l.name());
314 }
315 if find_node_at_offset::<ast::TypeParamList>(&syntax, left.text_range().start()).is_some() {
316 return left.ancestors().find_map(ast::StructDef::cast).and_then(|l| l.name());
317 }
318 }
319 None
320}
321
322fn is_call_expr_name_ref(name_ref: &ast::NameRef) -> bool {
323 name_ref
324 .syntax()
325 .ancestors()
326 .find_map(ast::CallExpr::cast)
327 .and_then(|c| match c.expr()? {
328 ast::Expr::PathExpr(p) => {
329 Some(p.path()?.segment()?.name_ref().as_ref() == Some(name_ref))
330 }
331 _ => None,
332 })
333 .unwrap_or(false)
334}
335
304#[cfg(test)] 336#[cfg(test)]
305mod tests { 337mod tests {
306 use crate::{ 338 use crate::{
@@ -309,7 +341,7 @@ mod tests {
309 }; 341 };
310 342
311 #[test] 343 #[test]
312 fn test_struct_literal() { 344 fn test_struct_literal_after_space() {
313 let code = r#" 345 let code = r#"
314 struct Foo <|>{ 346 struct Foo <|>{
315 a: i32, 347 a: i32,
@@ -331,6 +363,58 @@ mod tests {
331 } 363 }
332 364
333 #[test] 365 #[test]
366 fn test_struct_literal_befor_space() {
367 let code = r#"
368 struct Foo<|> {}
369 fn main() {
370 let f: Foo;
371 f = Foo {};
372 }"#;
373
374 let refs = get_all_refs(code);
375 check_result(
376 refs,
377 "Foo STRUCT_DEF FileId(1) [5; 18) [12; 15) Other",
378 &["FileId(1) [54; 57) Other", "FileId(1) [71; 74) StructLiteral"],
379 );
380 }
381
382 #[test]
383 fn test_struct_literal_with_generic_type() {
384 let code = r#"
385 struct Foo<T> <|>{}
386 fn main() {
387 let f: Foo::<i32>;
388 f = Foo {};
389 }"#;
390
391 let refs = get_all_refs(code);
392 check_result(
393 refs,
394 "Foo STRUCT_DEF FileId(1) [5; 21) [12; 15) Other",
395 &["FileId(1) [81; 84) StructLiteral"],
396 );
397 }
398
399 #[test]
400 fn test_struct_literal_for_tuple() {
401 let code = r#"
402 struct Foo<|>(i32);
403
404 fn main() {
405 let f: Foo;
406 f = Foo(1);
407 }"#;
408
409 let refs = get_all_refs(code);
410 check_result(
411 refs,
412 "Foo STRUCT_DEF FileId(1) [5; 21) [12; 15) Other",
413 &["FileId(1) [71; 74) StructLiteral"],
414 );
415 }
416
417 #[test]
334 fn test_find_all_refs_for_local() { 418 fn test_find_all_refs_for_local() {
335 let code = r#" 419 let code = r#"
336 fn main() { 420 fn main() {
@@ -564,7 +648,7 @@ mod tests {
564 check_result( 648 check_result(
565 refs, 649 refs,
566 "quux FN_DEF FileId(1) [18; 34) [25; 29) Other", 650 "quux FN_DEF FileId(1) [18; 34) [25; 29) Other",
567 &["FileId(2) [16; 20) Other", "FileId(3) [16; 20) Other"], 651 &["FileId(2) [16; 20) StructLiteral", "FileId(3) [16; 20) StructLiteral"],
568 ); 652 );
569 653
570 let refs = 654 let refs =
@@ -572,7 +656,7 @@ mod tests {
572 check_result( 656 check_result(
573 refs, 657 refs,
574 "quux FN_DEF FileId(1) [18; 34) [25; 29) Other", 658 "quux FN_DEF FileId(1) [18; 34) [25; 29) Other",
575 &["FileId(3) [16; 20) Other"], 659 &["FileId(3) [16; 20) StructLiteral"],
576 ); 660 );
577 } 661 }
578 662
@@ -591,7 +675,7 @@ mod tests {
591 check_result( 675 check_result(
592 refs, 676 refs,
593 "m1 MACRO_CALL FileId(1) [9; 63) [46; 48) Other", 677 "m1 MACRO_CALL FileId(1) [9; 63) [46; 48) Other",
594 &["FileId(1) [96; 98) Other", "FileId(1) [114; 116) Other"], 678 &["FileId(1) [96; 98) StructLiteral", "FileId(1) [114; 116) StructLiteral"],
595 ); 679 );
596 } 680 }
597 681
diff --git a/crates/ra_ide/src/runnables.rs b/crates/ra_ide/src/runnables.rs
index 7533692f6..8622dd956 100644
--- a/crates/ra_ide/src/runnables.rs
+++ b/crates/ra_ide/src/runnables.rs
@@ -43,7 +43,7 @@ fn runnable_fn(fn_def: ast::FnDef) -> Option<Runnable> {
43 let name = fn_def.name()?.text().clone(); 43 let name = fn_def.name()?.text().clone();
44 let kind = if name == "main" { 44 let kind = if name == "main" {
45 RunnableKind::Bin 45 RunnableKind::Bin
46 } else if fn_def.has_atom_attr("test") { 46 } else if has_test_related_attribute(&fn_def) {
47 RunnableKind::Test { name: name.to_string() } 47 RunnableKind::Test { name: name.to_string() }
48 } else if fn_def.has_atom_attr("bench") { 48 } else if fn_def.has_atom_attr("bench") {
49 RunnableKind::Bench { name: name.to_string() } 49 RunnableKind::Bench { name: name.to_string() }
@@ -53,6 +53,20 @@ fn runnable_fn(fn_def: ast::FnDef) -> Option<Runnable> {
53 Some(Runnable { range: fn_def.syntax().text_range(), kind }) 53 Some(Runnable { range: fn_def.syntax().text_range(), kind })
54} 54}
55 55
56/// This is a method with a heuristics to support test methods annotated with custom test annotations, such as
57/// `#[test_case(...)]`, `#[tokio::test]` and similar.
58/// Also a regular `#[test]` annotation is supported.
59///
60/// It may produce false positives, for example, `#[wasm_bindgen_test]` requires a different command to run the test,
61/// but it's better than not to have the runnables for the tests at all.
62fn has_test_related_attribute(fn_def: &ast::FnDef) -> bool {
63 fn_def
64 .attrs()
65 .filter_map(|attr| attr.path())
66 .map(|path| path.syntax().to_string().to_lowercase())
67 .any(|attribute_text| attribute_text.contains("test"))
68}
69
56fn runnable_mod(db: &RootDatabase, file_id: FileId, module: ast::Module) -> Option<Runnable> { 70fn runnable_mod(db: &RootDatabase, file_id: FileId, module: ast::Module) -> Option<Runnable> {
57 let has_test_function = module 71 let has_test_function = module
58 .item_list()? 72 .item_list()?
diff --git a/crates/ra_ide/src/snapshots/highlighting.html b/crates/ra_ide/src/snapshots/highlighting.html
index 1d130544f..1cc55e78b 100644
--- a/crates/ra_ide/src/snapshots/highlighting.html
+++ b/crates/ra_ide/src/snapshots/highlighting.html
@@ -34,6 +34,16 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
34 <span class="function">foo</span>::&lt;<span class="type.builtin">i32</span>&gt;(); 34 <span class="function">foo</span>::&lt;<span class="type.builtin">i32</span>&gt;();
35} 35}
36 36
37<span class="macro">macro_rules</span><span class="macro">!</span> def_fn {
38 ($($tt:tt)*) =&gt; {$($tt)*}
39}
40
41<span class="macro">def_fn</span><span class="macro">!</span>{
42 <span class="keyword">fn</span> <span class="function">bar</span>() -&gt; <span class="type.builtin">u32</span> {
43 <span class="literal.numeric">100</span>
44 }
45}
46
37<span class="comment">// comment</span> 47<span class="comment">// comment</span>
38<span class="keyword">fn</span> <span class="function">main</span>() { 48<span class="keyword">fn</span> <span class="function">main</span>() {
39 <span class="macro">println</span><span class="macro">!</span>(<span class="string">"Hello, {}!"</span>, <span class="literal.numeric">92</span>); 49 <span class="macro">println</span><span class="macro">!</span>(<span class="string">"Hello, {}!"</span>, <span class="literal.numeric">92</span>);
diff --git a/crates/ra_ide/src/snapshots/rainbow_highlighting.html b/crates/ra_ide/src/snapshots/rainbow_highlighting.html
index d90ee8540..918fd4b97 100644
--- a/crates/ra_ide/src/snapshots/rainbow_highlighting.html
+++ b/crates/ra_ide/src/snapshots/rainbow_highlighting.html
@@ -24,14 +24,14 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
24.keyword\.control { color: #F0DFAF; font-weight: bold; } 24.keyword\.control { color: #F0DFAF; font-weight: bold; }
25</style> 25</style>
26<pre><code><span class="keyword">fn</span> <span class="function">main</span>() { 26<pre><code><span class="keyword">fn</span> <span class="function">main</span>() {
27 <span class="keyword">let</span> <span class="variable" data-binding-hash="8723171760279909834" style="color: hsl(307,91%,75%);">hello</span> = <span class="string">"hello"</span>; 27 <span class="keyword">let</span> <span class="variable" data-binding-hash="2217585909179791122" style="color: hsl(280,74%,48%);">hello</span> = <span class="string">"hello"</span>;
28 <span class="keyword">let</span> <span class="variable" data-binding-hash="14702933417323009544" style="color: hsl(108,90%,49%);">x</span> = <span class="variable" data-binding-hash="8723171760279909834" style="color: hsl(307,91%,75%);">hello</span>.to_string(); 28 <span class="keyword">let</span> <span class="variable" data-binding-hash="4303609361109701698" style="color: hsl(242,75%,88%);">x</span> = <span class="variable" data-binding-hash="2217585909179791122" style="color: hsl(280,74%,48%);">hello</span>.to_string();
29 <span class="keyword">let</span> <span class="variable" data-binding-hash="5443150872754369068" style="color: hsl(215,43%,43%);">y</span> = <span class="variable" data-binding-hash="8723171760279909834" style="color: hsl(307,91%,75%);">hello</span>.to_string(); 29 <span class="keyword">let</span> <span class="variable" data-binding-hash="13865792086344377029" style="color: hsl(340,64%,86%);">y</span> = <span class="variable" data-binding-hash="2217585909179791122" style="color: hsl(280,74%,48%);">hello</span>.to_string();
30 30
31 <span class="keyword">let</span> <span class="variable" data-binding-hash="17358108296605513516" style="color: hsl(331,46%,60%);">x</span> = <span class="string">"other color please!"</span>; 31 <span class="keyword">let</span> <span class="variable" data-binding-hash="7011301204224269512" style="color: hsl(198,45%,40%);">x</span> = <span class="string">"other color please!"</span>;
32 <span class="keyword">let</span> <span class="variable" data-binding-hash="2073121142529774969" style="color: hsl(320,43%,74%);">y</span> = <span class="variable" data-binding-hash="17358108296605513516" style="color: hsl(331,46%,60%);">x</span>.to_string(); 32 <span class="keyword">let</span> <span class="variable" data-binding-hash="12461245066629867975" style="color: hsl(132,91%,68%);">y</span> = <span class="variable" data-binding-hash="7011301204224269512" style="color: hsl(198,45%,40%);">x</span>.to_string();
33} 33}
34 34
35<span class="keyword">fn</span> <span class="function">bar</span>() { 35<span class="keyword">fn</span> <span class="function">bar</span>() {
36 <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable.mut" data-binding-hash="8723171760279909834" style="color: hsl(307,91%,75%);">hello</span> = <span class="string">"hello"</span>; 36 <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable.mut" data-binding-hash="2217585909179791122" style="color: hsl(280,74%,48%);">hello</span> = <span class="string">"hello"</span>;
37}</code></pre> \ No newline at end of file 37}</code></pre> \ No newline at end of file
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs
index 0411977b9..530b984fc 100644
--- a/crates/ra_ide/src/syntax_highlighting.rs
+++ b/crates/ra_ide/src/syntax_highlighting.rs
@@ -1,14 +1,18 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use rustc_hash::{FxHashMap, FxHashSet}; 3use rustc_hash::FxHashMap;
4 4
5use hir::{InFile, Name, SourceBinder}; 5use hir::{HirFileId, InFile, Name, SourceAnalyzer, SourceBinder};
6use ra_db::SourceDatabase; 6use ra_db::SourceDatabase;
7use ra_prof::profile; 7use ra_prof::profile;
8use ra_syntax::{ast, AstNode, Direction, SyntaxElement, SyntaxKind, SyntaxKind::*, TextRange, T}; 8use ra_syntax::{
9 ast, AstNode, Direction, SyntaxElement, SyntaxKind, SyntaxKind::*, SyntaxToken, TextRange,
10 WalkEvent, T,
11};
9 12
10use crate::{ 13use crate::{
11 db::RootDatabase, 14 db::RootDatabase,
15 expand::descend_into_macros_with_analyzer,
12 references::{ 16 references::{
13 classify_name, classify_name_ref, 17 classify_name, classify_name_ref,
14 NameKind::{self, *}, 18 NameKind::{self, *},
@@ -72,121 +76,186 @@ pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec<HighlightedRa
72 let parse = db.parse(file_id); 76 let parse = db.parse(file_id);
73 let root = parse.tree().syntax().clone(); 77 let root = parse.tree().syntax().clone();
74 78
75 fn calc_binding_hash(file_id: FileId, name: &Name, shadow_count: u32) -> u64 { 79 let mut sb = SourceBinder::new(db);
76 fn hash<T: std::hash::Hash + std::fmt::Debug>(x: T) -> u64 { 80 let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default();
77 use std::{collections::hash_map::DefaultHasher, hash::Hasher}; 81 let mut res = Vec::new();
82 let analyzer = sb.analyze(InFile::new(file_id.into(), &root), None);
78 83
79 let mut hasher = DefaultHasher::new(); 84 let mut in_macro_call = None;
80 x.hash(&mut hasher); 85
81 hasher.finish() 86 for event in root.preorder_with_tokens() {
87 match event {
88 WalkEvent::Enter(node) => match node.kind() {
89 MACRO_CALL => {
90 in_macro_call = Some(node.clone());
91 if let Some(range) = highlight_macro(InFile::new(file_id.into(), node)) {
92 res.push(HighlightedRange { range, tag: tags::MACRO, binding_hash: None });
93 }
94 }
95 _ if in_macro_call.is_some() => {
96 if let Some(token) = node.as_token() {
97 if let Some((tag, binding_hash)) = highlight_token_tree(
98 db,
99 &mut sb,
100 &analyzer,
101 &mut bindings_shadow_count,
102 InFile::new(file_id.into(), token.clone()),
103 ) {
104 res.push(HighlightedRange {
105 range: node.text_range(),
106 tag,
107 binding_hash,
108 });
109 }
110 }
111 }
112 _ => {
113 if let Some((tag, binding_hash)) = highlight_node(
114 db,
115 &mut sb,
116 &mut bindings_shadow_count,
117 InFile::new(file_id.into(), node.clone()),
118 ) {
119 res.push(HighlightedRange { range: node.text_range(), tag, binding_hash });
120 }
121 }
122 },
123 WalkEvent::Leave(node) => {
124 if let Some(m) = in_macro_call.as_ref() {
125 if *m == node {
126 in_macro_call = None;
127 }
128 }
129 }
82 } 130 }
131 }
83 132
84 hash((file_id, name, shadow_count)) 133 res
134}
135
136fn highlight_macro(node: InFile<SyntaxElement>) -> Option<TextRange> {
137 let macro_call = ast::MacroCall::cast(node.value.as_node()?.clone())?;
138 let path = macro_call.path()?;
139 let name_ref = path.segment()?.name_ref()?;
140
141 let range_start = name_ref.syntax().text_range().start();
142 let mut range_end = name_ref.syntax().text_range().end();
143 for sibling in path.syntax().siblings_with_tokens(Direction::Next) {
144 match sibling.kind() {
145 T![!] | IDENT => range_end = sibling.text_range().end(),
146 _ => (),
147 }
85 } 148 }
86 149
87 let mut sb = SourceBinder::new(db); 150 Some(TextRange::from_to(range_start, range_end))
151}
88 152
89 // Visited nodes to handle highlighting priorities 153fn highlight_token_tree(
90 // FIXME: retain only ranges here 154 db: &RootDatabase,
91 let mut highlighted: FxHashSet<SyntaxElement> = FxHashSet::default(); 155 sb: &mut SourceBinder<RootDatabase>,
92 let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default(); 156 analyzer: &SourceAnalyzer,
157 bindings_shadow_count: &mut FxHashMap<Name, u32>,
158 token: InFile<SyntaxToken>,
159) -> Option<(&'static str, Option<u64>)> {
160 if token.value.parent().kind() != TOKEN_TREE {
161 return None;
162 }
163 let token = descend_into_macros_with_analyzer(db, analyzer, token);
164 let expanded = {
165 let parent = token.value.parent();
166 // We only care Name and Name_ref
167 match (token.value.kind(), parent.kind()) {
168 (IDENT, NAME) | (IDENT, NAME_REF) => token.with_value(parent.into()),
169 _ => token.map(|it| it.into()),
170 }
171 };
93 172
94 let mut res = Vec::new(); 173 highlight_node(db, sb, bindings_shadow_count, expanded)
95 for node in root.descendants_with_tokens() { 174}
96 if highlighted.contains(&node) { 175
97 continue; 176fn highlight_node(
177 db: &RootDatabase,
178 sb: &mut SourceBinder<RootDatabase>,
179 bindings_shadow_count: &mut FxHashMap<Name, u32>,
180 node: InFile<SyntaxElement>,
181) -> Option<(&'static str, Option<u64>)> {
182 let mut binding_hash = None;
183 let tag = match node.value.kind() {
184 FN_DEF => {
185 bindings_shadow_count.clear();
186 return None;
98 } 187 }
99 let mut binding_hash = None; 188 COMMENT => tags::LITERAL_COMMENT,
100 let tag = match node.kind() { 189 STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => tags::LITERAL_STRING,
101 FN_DEF => { 190 ATTR => tags::LITERAL_ATTRIBUTE,
102 bindings_shadow_count.clear(); 191 // Special-case field init shorthand
103 continue; 192 NAME_REF if node.value.parent().and_then(ast::RecordField::cast).is_some() => tags::FIELD,
104 } 193 NAME_REF if node.value.ancestors().any(|it| it.kind() == ATTR) => return None,
105 COMMENT => tags::LITERAL_COMMENT, 194 NAME_REF => {
106 STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => tags::LITERAL_STRING, 195 let name_ref = node.value.as_node().cloned().and_then(ast::NameRef::cast).unwrap();
107 ATTR => tags::LITERAL_ATTRIBUTE, 196 let name_kind = classify_name_ref(sb, node.with_value(&name_ref)).map(|d| d.kind);
108 // Special-case field init shorthand 197 match name_kind {
109 NAME_REF if node.parent().and_then(ast::RecordField::cast).is_some() => tags::FIELD, 198 Some(name_kind) => {
110 NAME_REF if node.ancestors().any(|it| it.kind() == ATTR) => continue, 199 if let Local(local) = &name_kind {
111 NAME_REF => { 200 if let Some(name) = local.name(db) {
112 let name_ref = node.as_node().cloned().and_then(ast::NameRef::cast).unwrap(); 201 let shadow_count =
113 let name_kind = classify_name_ref(&mut sb, InFile::new(file_id.into(), &name_ref)) 202 bindings_shadow_count.entry(name.clone()).or_default();
114 .map(|d| d.kind); 203 binding_hash =
115 match name_kind { 204 Some(calc_binding_hash(node.file_id, &name, *shadow_count))
116 Some(name_kind) => { 205 }
117 if let Local(local) = &name_kind { 206 };
118 if let Some(name) = local.name(db) { 207
119 let shadow_count = 208 highlight_name(db, name_kind)
120 bindings_shadow_count.entry(name.clone()).or_default();
121 binding_hash =
122 Some(calc_binding_hash(file_id, &name, *shadow_count))
123 }
124 };
125
126 highlight_name(db, name_kind)
127 }
128 _ => continue,
129 }
130 }
131 NAME => {
132 let name = node.as_node().cloned().and_then(ast::Name::cast).unwrap();
133 let name_kind =
134 classify_name(&mut sb, InFile::new(file_id.into(), &name)).map(|d| d.kind);
135
136 if let Some(Local(local)) = &name_kind {
137 if let Some(name) = local.name(db) {
138 let shadow_count = bindings_shadow_count.entry(name.clone()).or_default();
139 *shadow_count += 1;
140 binding_hash = Some(calc_binding_hash(file_id, &name, *shadow_count))
141 }
142 };
143
144 match name_kind {
145 Some(name_kind) => highlight_name(db, name_kind),
146 None => name.syntax().parent().map_or(tags::FUNCTION, |x| match x.kind() {
147 STRUCT_DEF | ENUM_DEF | TRAIT_DEF | TYPE_ALIAS_DEF => tags::TYPE,
148 TYPE_PARAM => tags::TYPE_PARAM,
149 RECORD_FIELD_DEF => tags::FIELD,
150 _ => tags::FUNCTION,
151 }),
152 } 209 }
210 _ => return None,
153 } 211 }
154 INT_NUMBER | FLOAT_NUMBER => tags::LITERAL_NUMERIC, 212 }
155 BYTE => tags::LITERAL_BYTE, 213 NAME => {
156 CHAR => tags::LITERAL_CHAR, 214 let name = node.value.as_node().cloned().and_then(ast::Name::cast).unwrap();
157 LIFETIME => tags::TYPE_LIFETIME, 215 let name_kind = classify_name(sb, node.with_value(&name)).map(|d| d.kind);
158 T![unsafe] => tags::KEYWORD_UNSAFE, 216
159 k if is_control_keyword(k) => tags::KEYWORD_CONTROL, 217 if let Some(Local(local)) = &name_kind {
160 k if k.is_keyword() => tags::KEYWORD, 218 if let Some(name) = local.name(db) {
161 _ => { 219 let shadow_count = bindings_shadow_count.entry(name.clone()).or_default();
162 if let Some(macro_call) = node.as_node().cloned().and_then(ast::MacroCall::cast) { 220 *shadow_count += 1;
163 if let Some(path) = macro_call.path() { 221 binding_hash = Some(calc_binding_hash(node.file_id, &name, *shadow_count))
164 if let Some(segment) = path.segment() {
165 if let Some(name_ref) = segment.name_ref() {
166 highlighted.insert(name_ref.syntax().clone().into());
167 let range_start = name_ref.syntax().text_range().start();
168 let mut range_end = name_ref.syntax().text_range().end();
169 for sibling in path.syntax().siblings_with_tokens(Direction::Next) {
170 match sibling.kind() {
171 T![!] | IDENT => range_end = sibling.text_range().end(),
172 _ => (),
173 }
174 }
175 res.push(HighlightedRange {
176 range: TextRange::from_to(range_start, range_end),
177 tag: tags::MACRO,
178 binding_hash: None,
179 })
180 }
181 }
182 }
183 } 222 }
184 continue; 223 };
224
225 match name_kind {
226 Some(name_kind) => highlight_name(db, name_kind),
227 None => name.syntax().parent().map_or(tags::FUNCTION, |x| match x.kind() {
228 STRUCT_DEF | ENUM_DEF | TRAIT_DEF | TYPE_ALIAS_DEF => tags::TYPE,
229 TYPE_PARAM => tags::TYPE_PARAM,
230 RECORD_FIELD_DEF => tags::FIELD,
231 _ => tags::FUNCTION,
232 }),
185 } 233 }
186 }; 234 }
187 res.push(HighlightedRange { range: node.text_range(), tag, binding_hash }) 235 INT_NUMBER | FLOAT_NUMBER => tags::LITERAL_NUMERIC,
236 BYTE => tags::LITERAL_BYTE,
237 CHAR => tags::LITERAL_CHAR,
238 LIFETIME => tags::TYPE_LIFETIME,
239 T![unsafe] => tags::KEYWORD_UNSAFE,
240 k if is_control_keyword(k) => tags::KEYWORD_CONTROL,
241 k if k.is_keyword() => tags::KEYWORD,
242
243 _ => return None,
244 };
245
246 return Some((tag, binding_hash));
247
248 fn calc_binding_hash(file_id: HirFileId, name: &Name, shadow_count: u32) -> u64 {
249 fn hash<T: std::hash::Hash + std::fmt::Debug>(x: T) -> u64 {
250 use std::{collections::hash_map::DefaultHasher, hash::Hasher};
251
252 let mut hasher = DefaultHasher::new();
253 x.hash(&mut hasher);
254 hasher.finish()
255 }
256
257 hash((file_id, name, shadow_count))
188 } 258 }
189 res
190} 259}
191 260
192pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: bool) -> String { 261pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: bool) -> String {
@@ -331,6 +400,16 @@ fn foo<T>() -> T {
331 foo::<i32>(); 400 foo::<i32>();
332} 401}
333 402
403macro_rules! def_fn {
404 ($($tt:tt)*) => {$($tt)*}
405}
406
407def_fn!{
408 fn bar() -> u32 {
409 100
410 }
411}
412
334// comment 413// comment
335fn main() { 414fn main() {
336 println!("Hello, {}!", 92); 415 println!("Hello, {}!", 92);