aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide/src')
-rw-r--r--crates/ide/src/call_hierarchy.rs51
-rw-r--r--crates/ide/src/display/navigation_target.rs14
-rw-r--r--crates/ide/src/doc_links.rs34
-rw-r--r--crates/ide/src/goto_definition.rs93
-rw-r--r--crates/ide/src/hover.rs47
-rw-r--r--crates/ide/src/inlay_hints.rs62
-rw-r--r--crates/ide/src/lib.rs10
-rw-r--r--crates/ide/src/references.rs93
-rw-r--r--crates/ide/src/references/rename.rs341
-rw-r--r--crates/ide/src/runnables.rs110
-rw-r--r--crates/ide/src/syntax_highlighting.rs821
-rw-r--r--crates/ide/src/syntax_highlighting/format.rs94
-rw-r--r--crates/ide/src/syntax_highlighting/highlight.rs530
-rw-r--r--crates/ide/src/syntax_highlighting/highlights.rs92
-rw-r--r--crates/ide/src/syntax_highlighting/html.rs28
-rw-r--r--crates/ide/src/syntax_highlighting/inject.rs158
-rw-r--r--crates/ide/src/syntax_highlighting/injection.rs240
-rw-r--r--crates/ide/src/syntax_highlighting/injector.rs78
-rw-r--r--crates/ide/src/syntax_highlighting/macro_rules.rs10
-rw-r--r--crates/ide/src/syntax_highlighting/tags.rs173
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html28
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html71
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html4
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_injection.html20
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_strings.html106
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html88
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlighting.html260
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/injection.html16
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/rainbow_highlighting.html20
-rw-r--r--crates/ide/src/syntax_highlighting/tests.rs5
30 files changed, 1952 insertions, 1745 deletions
<
diff --git a/crates/ide/src/call_hierarchy.rs b/crates/ide/src/call_hierarchy.rs
index 4d8983cb2..e8999a7f3 100644
--- a/crates/ide/src/call_hierarchy.rs
+++ b/crates/ide/src/call_hierarchy.rs
@@ -5,7 +5,7 @@ use indexmap::IndexMap;
5use hir::Semantics; 5use hir::Semantics;
6use ide_db::call_info::FnCallNode; 6use ide_db::call_info::FnCallNode;
7use ide_db::RootDatabase; 7use ide_db::RootDatabase;
8use syntax::{ast, match_ast, AstNode, TextRange}; 8use syntax::{ast, AstNode, TextRange};
9 9
10use crate::{ 10use crate::{
11 display::TryToNav, goto_definition, references, FilePosition, NavigationTarget, RangeInfo, 11 display::TryToNav, goto_definition, references, FilePosition, NavigationTarget, RangeInfo,
@@ -47,28 +47,23 @@ pub(crate) fn incoming_calls(db: &RootDatabase, position: FilePosition) -> Optio
47 47
48 let mut calls = CallLocations::default(); 48 let mut calls = CallLocations::default();
49 49
50 for reference in refs.info.references() { 50 for (&file_id, references) in refs.info.references().iter() {
51 let file_id = reference.file_range.file_id;
52 let file = sema.parse(file_id); 51 let file = sema.parse(file_id);
53 let file = file.syntax(); 52 let file = file.syntax();
54 let token = file.token_at_offset(reference.file_range.range.start()).next()?; 53 for reference in references {
55 let token = sema.descend_into_macros(token); 54 let token = file.token_at_offset(reference.range.start()).next()?;
56 let syntax = token.parent(); 55 let token = sema.descend_into_macros(token);
57 56 let syntax = token.parent();
58 // This target is the containing function 57
59 if let Some(nav) = syntax.ancestors().find_map(|node| { 58 // This target is the containing function
60 match_ast! { 59 if let Some(nav) = syntax.ancestors().find_map(|node| {
61 match node { 60 let fn_ = ast::Fn::cast(node)?;
62 ast::Fn(it) => { 61 let def = sema.to_def(&fn_)?;
63 let def = sema.to_def(&it)?; 62 def.try_to_nav(sema.db)
64 def.try_to_nav(sema.db) 63 }) {
65 }, 64 let relative_range = reference.range;
66 _ => None, 65 calls.add(&nav, relative_range);
67 }
68 } 66 }
69 }) {
70 let relative_range = reference.file_range.range;
71 calls.add(&nav, relative_range);
72 } 67 }
73 } 68 }
74 69
@@ -91,17 +86,12 @@ pub(crate) fn outgoing_calls(db: &RootDatabase, position: FilePosition) -> Optio
91 .filter_map(|node| FnCallNode::with_node_exact(&node)) 86 .filter_map(|node| FnCallNode::with_node_exact(&node))
92 .filter_map(|call_node| { 87 .filter_map(|call_node| {
93 let name_ref = call_node.name_ref()?; 88 let name_ref = call_node.name_ref()?;
94 89 let func_target = match call_node {
95 if let Some(func_target) = match &call_node {
96 FnCallNode::CallExpr(expr) => { 90 FnCallNode::CallExpr(expr) => {
97 //FIXME: Type::as_callable is broken 91 //FIXME: Type::as_callable is broken
98 let callable = sema.type_of_expr(&expr.expr()?)?.as_callable(db)?; 92 let callable = sema.type_of_expr(&expr.expr()?)?.as_callable(db)?;
99 match callable.kind() { 93 match callable.kind() {
100 hir::CallableKind::Function(it) => { 94 hir::CallableKind::Function(it) => it.try_to_nav(db),
101 let fn_def: hir::Function = it.into();
102 let nav = fn_def.try_to_nav(db)?;
103 Some(nav)
104 }
105 _ => None, 95 _ => None,
106 } 96 }
107 } 97 }
@@ -109,11 +99,8 @@ pub(crate) fn outgoing_calls(db: &RootDatabase, position: FilePosition) -> Optio
109 let function = sema.resolve_method_call(&expr)?; 99 let function = sema.resolve_method_call(&expr)?;
110 function.try_to_nav(db) 100 function.try_to_nav(db)
111 } 101 }
112 } { 102 }?;
113 Some((func_target, name_ref.syntax().text_range())) 103 Some((func_target, name_ref.syntax().text_range()))
114 } else {
115 None
116 }
117 }) 104 })
118 .for_each(|(nav, range)| calls.add(&nav, range)); 105 .for_each(|(nav, range)| calls.add(&nav, range));
119 106
diff --git a/crates/ide/src/display/navigation_target.rs b/crates/ide/src/display/navigation_target.rs
index e24c78301..4eecae697 100644
--- a/crates/ide/src/display/navigation_target.rs
+++ b/crates/ide/src/display/navigation_target.rs
@@ -215,10 +215,8 @@ impl TryToNav for Definition {
215 Definition::ModuleDef(it) => it.try_to_nav(db), 215 Definition::ModuleDef(it) => it.try_to_nav(db),
216 Definition::SelfType(it) => it.try_to_nav(db), 216 Definition::SelfType(it) => it.try_to_nav(db),
217 Definition::Local(it) => Some(it.to_nav(db)), 217 Definition::Local(it) => Some(it.to_nav(db)),
218 Definition::TypeParam(it) => it.try_to_nav(db), 218 Definition::GenericParam(it) => it.try_to_nav(db),
219 Definition::LifetimeParam(it) => it.try_to_nav(db),
220 Definition::Label(it) => Some(it.to_nav(db)), 219 Definition::Label(it) => Some(it.to_nav(db)),
221 Definition::ConstParam(it) => it.try_to_nav(db),
222 } 220 }
223 } 221 }
224} 222}
@@ -389,6 +387,16 @@ impl TryToNav for hir::AssocItem {
389 } 387 }
390} 388}
391 389
390impl TryToNav for hir::GenericParam {
391 fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
392 match self {
393 hir::GenericParam::TypeParam(it) => it.try_to_nav(db),
394 hir::GenericParam::ConstParam(it) => it.try_to_nav(db),
395 hir::GenericParam::LifetimeParam(it) => it.try_to_nav(db),
396 }
397 }
398}
399
392impl ToNav for hir::Local { 400impl ToNav for hir::Local {
393 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { 401 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
394 let src = self.source(db); 402 let src = self.source(db);
diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs
index 1ff818de2..de10406bc 100644
--- a/crates/ide/src/doc_links.rs
+++ b/crates/ide/src/doc_links.rs
@@ -1,6 +1,6 @@
1//! Resolves and rewrites links in markdown documentation. 1//! Resolves and rewrites links in markdown documentation.
2 2
3use std::{convert::TryFrom, iter::once}; 3use std::{convert::TryFrom, iter::once, ops::Range};
4 4
5use itertools::Itertools; 5use itertools::Itertools;
6use pulldown_cmark::{BrokenLink, CowStr, Event, InlineStr, LinkType, Options, Parser, Tag}; 6use pulldown_cmark::{BrokenLink, CowStr, Event, InlineStr, LinkType, Options, Parser, Tag};
@@ -39,7 +39,7 @@ pub(crate) fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Defi
39 if target.contains("://") { 39 if target.contains("://") {
40 (target.to_string(), title.to_string()) 40 (target.to_string(), title.to_string())
41 } else { 41 } else {
42 // Two posibilities: 42 // Two possibilities:
43 // * path-based links: `../../module/struct.MyStruct.html` 43 // * path-based links: `../../module/struct.MyStruct.html`
44 // * module-based links (AKA intra-doc links): `super::super::module::MyStruct` 44 // * module-based links (AKA intra-doc links): `super::super::module::MyStruct`
45 if let Some(rewritten) = rewrite_intra_doc_link(db, *definition, target, title) { 45 if let Some(rewritten) = rewrite_intra_doc_link(db, *definition, target, title) {
@@ -61,6 +61,30 @@ pub(crate) fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Defi
61 out 61 out
62} 62}
63 63
64pub(crate) fn extract_definitions_from_markdown(
65 markdown: &str,
66) -> Vec<(String, Option<hir::Namespace>, Range<usize>)> {
67 let mut res = vec![];
68 let mut cb = |link: BrokenLink| {
69 Some((
70 /*url*/ link.reference.to_owned().into(),
71 /*title*/ link.reference.to_owned().into(),
72 ))
73 };
74 let doc = Parser::new_with_broken_link_callback(markdown, Options::empty(), Some(&mut cb));
75 for (event, range) in doc.into_offset_iter() {
76 match event {
77 Event::Start(Tag::Link(_link_type, ref target, ref title)) => {
78 let link = if target.is_empty() { title } else { target };
79 let (link, ns) = parse_link(link);
80 res.push((link.to_string(), ns, range));
81 }
82 _ => {}
83 }
84 }
85 res
86}
87
64/// Remove all links in markdown documentation. 88/// Remove all links in markdown documentation.
65pub(crate) fn remove_links(markdown: &str) -> String { 89pub(crate) fn remove_links(markdown: &str) -> String {
66 let mut drop_link = false; 90 let mut drop_link = false;
@@ -192,9 +216,7 @@ fn rewrite_intra_doc_link(
192 Definition::Field(it) => it.resolve_doc_path(db, link, ns), 216 Definition::Field(it) => it.resolve_doc_path(db, link, ns),
193 Definition::SelfType(_) 217 Definition::SelfType(_)
194 | Definition::Local(_) 218 | Definition::Local(_)
195 | Definition::TypeParam(_) 219 | Definition::GenericParam(_)
196 | Definition::ConstParam(_)
197 | Definition::LifetimeParam(_)
198 | Definition::Label(_) => return None, 220 | Definition::Label(_) => return None,
199 }?; 221 }?;
200 let krate = resolved.module(db)?.krate(); 222 let krate = resolved.module(db)?.krate();
@@ -420,7 +442,7 @@ fn get_symbol_fragment(db: &dyn HirDatabase, field_or_assoc: &FieldOrAssocItem)
420 function.as_assoc_item(db).map(|assoc| assoc.container(db)), 442 function.as_assoc_item(db).map(|assoc| assoc.container(db)),
421 Some(AssocItemContainer::Trait(..)) 443 Some(AssocItemContainer::Trait(..))
422 ); 444 );
423 // This distinction may get more complicated when specialisation is available. 445 // This distinction may get more complicated when specialization is available.
424 // Rustdoc makes this decision based on whether a method 'has defaultness'. 446 // Rustdoc makes this decision based on whether a method 'has defaultness'.
425 // Currently this is only the case for provided trait methods. 447 // Currently this is only the case for provided trait methods.
426 if is_trait_method && !function.has_body(db) { 448 if is_trait_method && !function.has_body(db) {
diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs
index 95b4cb9e3..cd4afc804 100644
--- a/crates/ide/src/goto_definition.rs
+++ b/crates/ide/src/goto_definition.rs
@@ -1,14 +1,18 @@
1use either::Either; 1use either::Either;
2use hir::Semantics; 2use hir::{HasAttrs, ModuleDef, Semantics};
3use ide_db::{ 3use ide_db::{
4 base_db::FileId, 4 base_db::FileId,
5 defs::{NameClass, NameRefClass}, 5 defs::{Definition, NameClass, NameRefClass},
6 symbol_index, RootDatabase, 6 symbol_index, RootDatabase,
7}; 7};
8use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset, T}; 8use syntax::{
9 ast, match_ast, AstNode, AstToken, SyntaxKind::*, SyntaxToken, TextSize, TokenAtOffset, T,
10};
9 11
10use crate::{ 12use crate::{
11 display::{ToNav, TryToNav}, 13 display::{ToNav, TryToNav},
14 doc_links::extract_definitions_from_markdown,
15 runnables::doc_owner_to_def,
12 FilePosition, NavigationTarget, RangeInfo, SymbolKind, 16 FilePosition, NavigationTarget, RangeInfo, SymbolKind,
13}; 17};
14 18
@@ -30,6 +34,10 @@ pub(crate) fn goto_definition(
30 let original_token = pick_best(file.token_at_offset(position.offset))?; 34 let original_token = pick_best(file.token_at_offset(position.offset))?;
31 let token = sema.descend_into_macros(original_token.clone()); 35 let token = sema.descend_into_macros(original_token.clone());
32 let parent = token.parent(); 36 let parent = token.parent();
37 if let Some(comment) = ast::Comment::cast(token.clone()) {
38 let nav = def_for_doc_comment(&sema, position, &comment)?.try_to_nav(db)?;
39 return Some(RangeInfo::new(original_token.text_range(), vec![nav]));
40 }
33 41
34 let nav_targets = match_ast! { 42 let nav_targets = match_ast! {
35 match parent { 43 match parent {
@@ -68,11 +76,58 @@ pub(crate) fn goto_definition(
68 Some(RangeInfo::new(original_token.text_range(), nav_targets)) 76 Some(RangeInfo::new(original_token.text_range(), nav_targets))
69} 77}
70 78
79fn def_for_doc_comment(
80 sema: &Semantics<RootDatabase>,
81 position: FilePosition,
82 doc_comment: &ast::Comment,
83) -> Option<hir::ModuleDef> {
84 let parent = doc_comment.syntax().parent();
85 let (link, ns) = extract_positioned_link_from_comment(position, doc_comment)?;
86
87 let def = doc_owner_to_def(sema, parent)?;
88 match def {
89 Definition::ModuleDef(def) => match def {
90 ModuleDef::Module(it) => it.resolve_doc_path(sema.db, &link, ns),
91 ModuleDef::Function(it) => it.resolve_doc_path(sema.db, &link, ns),
92 ModuleDef::Adt(it) => it.resolve_doc_path(sema.db, &link, ns),
93 ModuleDef::Variant(it) => it.resolve_doc_path(sema.db, &link, ns),
94 ModuleDef::Const(it) => it.resolve_doc_path(sema.db, &link, ns),
95 ModuleDef::Static(it) => it.resolve_doc_path(sema.db, &link, ns),
96 ModuleDef::Trait(it) => it.resolve_doc_path(sema.db, &link, ns),
97 ModuleDef::TypeAlias(it) => it.resolve_doc_path(sema.db, &link, ns),
98 ModuleDef::BuiltinType(_) => return None,
99 },
100 Definition::Macro(it) => it.resolve_doc_path(sema.db, &link, ns),
101 Definition::Field(it) => it.resolve_doc_path(sema.db, &link, ns),
102 Definition::SelfType(_)
103 | Definition::Local(_)
104 | Definition::GenericParam(_)
105 | Definition::Label(_) => return None,
106 }
107}
108
109fn extract_positioned_link_from_comment(
110 position: FilePosition,
111 comment: &ast::Comment,
112) -> Option<(String, Option<hir::Namespace>)> {
113 let comment_range = comment.syntax().text_range();
114 let doc_comment = comment.doc_comment()?;
115 let def_links = extract_definitions_from_markdown(doc_comment);
116 let (def_link, ns, _) = def_links.iter().min_by_key(|(_, _, def_link_range)| {
117 let matched_position = comment_range.start() + TextSize::from(def_link_range.start as u32);
118 match position.offset.checked_sub(matched_position) {
119 Some(distance) => distance,
120 None => comment_range.end(),
121 }
122 })?;
123 Some((def_link.to_string(), ns.clone()))
124}
125
71fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { 126fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
72 return tokens.max_by_key(priority); 127 return tokens.max_by_key(priority);
73 fn priority(n: &SyntaxToken) -> usize { 128 fn priority(n: &SyntaxToken) -> usize {
74 match n.kind() { 129 match n.kind() {
75 IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] => 2, 130 IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | COMMENT => 2,
76 kind if kind.is_trivia() => 0, 131 kind if kind.is_trivia() => 0,
77 _ => 1, 132 _ => 1,
78 } 133 }
@@ -1145,4 +1200,34 @@ fn foo<'foo>(_: &'foo ()) {
1145}"#, 1200}"#,
1146 ) 1201 )
1147 } 1202 }
1203
1204 #[test]
1205 fn goto_def_for_intra_doc_link_same_file() {
1206 check(
1207 r#"
1208/// Blah, [`bar`](bar) .. [`foo`](foo)$0 has [`bar`](bar)
1209pub fn bar() { }
1210
1211/// You might want to see [`std::fs::read()`] too.
1212pub fn foo() { }
1213 //^^^
1214
1215}"#,
1216 )
1217 }
1218
1219 #[test]
1220 fn goto_def_for_intra_doc_link_inner() {
1221 check(
1222 r#"
1223//- /main.rs
1224mod m;
1225struct S;
1226 //^
1227
1228//- /m.rs
1229//! [`super::S$0`]
1230"#,
1231 )
1232 }
1148} 1233}
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index 8cb4a51d8..317b6f011 100644
--- a/crates/ide/src/hover.rs
+++ b/crates/ide/src/hover.rs
@@ -1,6 +1,6 @@
1use hir::{ 1use hir::{
2 Adt, AsAssocItem, AssocItemContainer, FieldSource, HasAttrs, HasSource, HirDisplay, Module, 2 Adt, AsAssocItem, AssocItemContainer, FieldSource, GenericParam, HasAttrs, HasSource,
3 ModuleDef, ModuleSource, Semantics, 3 HirDisplay, Module, ModuleDef, ModuleSource, Semantics,
4}; 4};
5use ide_db::base_db::SourceDatabase; 5use ide_db::base_db::SourceDatabase;
6use ide_db::{ 6use ide_db::{
@@ -17,7 +17,7 @@ use crate::{
17 doc_links::{remove_links, rewrite_links}, 17 doc_links::{remove_links, rewrite_links},
18 markdown_remove::remove_markdown, 18 markdown_remove::remove_markdown,
19 markup::Markup, 19 markup::Markup,
20 runnables::{runnable, runnable_fn}, 20 runnables::{runnable_fn, runnable_mod},
21 FileId, FilePosition, NavigationTarget, RangeInfo, Runnable, 21 FileId, FilePosition, NavigationTarget, RangeInfo, Runnable,
22}; 22};
23 23
@@ -175,12 +175,7 @@ fn show_implementations_action(db: &RootDatabase, def: Definition) -> Option<Hov
175 Definition::SelfType(it) => it.target_ty(db).as_adt(), 175 Definition::SelfType(it) => it.target_ty(db).as_adt(),
176 _ => None, 176 _ => None,
177 }?; 177 }?;
178 match adt { 178 adt.try_to_nav(db).map(to_action)
179 Adt::Struct(it) => it.try_to_nav(db),
180 Adt::Union(it) => it.try_to_nav(db),
181 Adt::Enum(it) => it.try_to_nav(db),
182 }
183 .map(to_action)
184} 179}
185 180
186fn runnable_action( 181fn runnable_action(
@@ -192,7 +187,7 @@ fn runnable_action(
192 Definition::ModuleDef(it) => match it { 187 Definition::ModuleDef(it) => match it {
193 ModuleDef::Module(it) => match it.definition_source(sema.db).value { 188 ModuleDef::Module(it) => match it.definition_source(sema.db).value {
194 ModuleSource::Module(it) => { 189 ModuleSource::Module(it) => {
195 runnable(&sema, it.syntax().clone()).map(|it| HoverAction::Runnable(it)) 190 runnable_mod(&sema, it).map(|it| HoverAction::Runnable(it))
196 } 191 }
197 _ => None, 192 _ => None,
198 }, 193 },
@@ -220,12 +215,12 @@ fn goto_type_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
220 } 215 }
221 }; 216 };
222 217
223 if let Definition::TypeParam(it) = def { 218 if let Definition::GenericParam(GenericParam::TypeParam(it)) = def {
224 it.trait_bounds(db).into_iter().for_each(|it| push_new_def(it.into())); 219 it.trait_bounds(db).into_iter().for_each(|it| push_new_def(it.into()));
225 } else { 220 } else {
226 let ty = match def { 221 let ty = match def {
227 Definition::Local(it) => it.ty(db), 222 Definition::Local(it) => it.ty(db),
228 Definition::ConstParam(it) => it.ty(db), 223 Definition::GenericParam(GenericParam::ConstParam(it)) => it.ty(db),
229 _ => return None, 224 _ => return None,
230 }; 225 };
231 226
@@ -357,9 +352,11 @@ fn hover_for_definition(db: &RootDatabase, def: Definition) -> Option<Markup> {
357 }) 352 })
358 } 353 }
359 Definition::Label(it) => Some(Markup::fenced_block(&it.name(db))), 354 Definition::Label(it) => Some(Markup::fenced_block(&it.name(db))),
360 Definition::LifetimeParam(it) => Some(Markup::fenced_block(&it.name(db))), 355 Definition::GenericParam(it) => match it {
361 Definition::TypeParam(type_param) => Some(Markup::fenced_block(&type_param.display(db))), 356 GenericParam::TypeParam(it) => Some(Markup::fenced_block(&it.display(db))),
362 Definition::ConstParam(it) => from_def_source(db, it, None), 357 GenericParam::LifetimeParam(it) => Some(Markup::fenced_block(&it.name(db))),
358 GenericParam::ConstParam(it) => from_def_source(db, it, None),
359 },
363 }; 360 };
364 361
365 fn from_def_source<A, D>(db: &RootDatabase, def: D, mod_path: Option<String>) -> Option<Markup> 362 fn from_def_source<A, D>(db: &RootDatabase, def: D, mod_path: Option<String>) -> Option<Markup>
@@ -1951,16 +1948,16 @@ struct S {
1951/// Test cases: 1948/// Test cases:
1952/// case 1. bare URL: https://www.example.com/ 1949/// case 1. bare URL: https://www.example.com/
1953/// case 2. inline URL with title: [example](https://www.example.com/) 1950/// case 2. inline URL with title: [example](https://www.example.com/)
1954/// case 3. code refrence: [`Result`] 1951/// case 3. code reference: [`Result`]
1955/// case 4. code refrence but miss footnote: [`String`] 1952/// case 4. code reference but miss footnote: [`String`]
1956/// case 5. autolink: <http://www.example.com/> 1953/// case 5. autolink: <http://www.example.com/>
1957/// case 6. email address: <[email protected]> 1954/// case 6. email address: <[email protected]>
1958/// case 7. refrence: [example][example] 1955/// case 7. reference: [example][example]
1959/// case 8. collapsed link: [example][] 1956/// case 8. collapsed link: [example][]
1960/// case 9. shortcut link: [example] 1957/// case 9. shortcut link: [example]
1961/// case 10. inline without URL: [example]() 1958/// case 10. inline without URL: [example]()
1962/// case 11. refrence: [foo][foo] 1959/// case 11. reference: [foo][foo]
1963/// case 12. refrence: [foo][bar] 1960/// case 12. reference: [foo][bar]
1964/// case 13. collapsed link: [foo][] 1961/// case 13. collapsed link: [foo][]
1965/// case 14. shortcut link: [foo] 1962/// case 14. shortcut link: [foo]
1966/// case 15. inline without URL: [foo]() 1963/// case 15. inline without URL: [foo]()
@@ -1987,16 +1984,16 @@ pub fn fo$0o() {}
1987 Test cases: 1984 Test cases:
1988 case 1. bare URL: https://www.example.com/ 1985 case 1. bare URL: https://www.example.com/
1989 case 2. inline URL with title: [example](https://www.example.com/) 1986 case 2. inline URL with title: [example](https://www.example.com/)
1990 case 3. code refrence: `Result` 1987 case 3. code reference: `Result`
1991 case 4. code refrence but miss footnote: `String` 1988 case 4. code reference but miss footnote: `String`
1992 case 5. autolink: http://www.example.com/ 1989 case 5. autolink: http://www.example.com/
1993 case 6. email address: [email protected] 1990 case 6. email address: [email protected]
1994 case 7. refrence: example 1991 case 7. reference: example
1995 case 8. collapsed link: example 1992 case 8. collapsed link: example
1996 case 9. shortcut link: example 1993 case 9. shortcut link: example
1997 case 10. inline without URL: example 1994 case 10. inline without URL: example
1998 case 11. refrence: foo 1995 case 11. reference: foo
1999 case 12. refrence: foo 1996 case 12. reference: foo
2000 case 13. collapsed link: foo 1997 case 13. collapsed link: foo
2001 case 14. shortcut link: foo 1998 case 14. shortcut link: foo
2002 case 15. inline without URL: foo 1999 case 15. inline without URL: foo
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs
index fe60abfc8..a2039fcc7 100644
--- a/crates/ide/src/inlay_hints.rs
+++ b/crates/ide/src/inlay_hints.rs
@@ -353,9 +353,25 @@ fn is_argument_similar_to_param_name(
353 } 353 }
354 match get_string_representation(argument) { 354 match get_string_representation(argument) {
355 None => false, 355 None => false,
356 Some(repr) => { 356 Some(argument_string) => {
357 let argument_string = repr.trim_start_matches('_'); 357 let num_leading_underscores =
358 argument_string.starts_with(param_name) || argument_string.ends_with(param_name) 358 argument_string.bytes().take_while(|&c| c == b'_').count();
359
360 // Does the argument name begin with the parameter name? Ignore leading underscores.
361 let mut arg_bytes = argument_string.bytes().skip(num_leading_underscores);
362 let starts_with_pattern = param_name.bytes().all(
363 |expected| matches!(arg_bytes.next(), Some(actual) if expected.eq_ignore_ascii_case(&actual)),
364 );
365
366 if starts_with_pattern {
367 return true;
368 }
369
370 // Does the argument name end with the parameter name?
371 let mut arg_bytes = argument_string.bytes().skip(num_leading_underscores);
372 param_name.bytes().rev().all(
373 |expected| matches!(arg_bytes.next_back(), Some(actual) if expected.eq_ignore_ascii_case(&actual)),
374 )
359 } 375 }
360 } 376 }
361} 377}
@@ -901,6 +917,9 @@ fn main() {
901 twiddle(true); 917 twiddle(true);
902 doo(true); 918 doo(true);
903 919
920 const TWIDDLE_UPPERCASE: bool = true;
921 twiddle(TWIDDLE_UPPERCASE);
922
904 let mut param_begin: Param = Param {}; 923 let mut param_begin: Param = Param {};
905 different_order(&param_begin); 924 different_order(&param_begin);
906 different_order(&mut param_begin); 925 different_order(&mut param_begin);
@@ -1382,4 +1401,41 @@ fn main() {
1382"#, 1401"#,
1383 ) 1402 )
1384 } 1403 }
1404
1405 #[test]
1406 fn fn_hints() {
1407 check(
1408 r#"
1409trait Sized {}
1410
1411fn foo() -> impl Fn() { loop {} }
1412fn foo1() -> impl Fn(f64) { loop {} }
1413fn foo2() -> impl Fn(f64, f64) { loop {} }
1414fn foo3() -> impl Fn(f64, f64) -> u32 { loop {} }
1415fn foo4() -> &'static dyn Fn(f64, f64) -> u32 { loop {} }
1416fn foo5() -> &'static dyn Fn(&'static dyn Fn(f64, f64) -> u32, f64) -> u32 { loop {} }
1417fn foo6() -> impl Fn(f64, f64) -> u32 + Sized { loop {} }
1418fn foo7() -> *const (impl Fn(f64, f64) -> u32 + Sized) { loop {} }
1419
1420fn main() {
1421 let foo = foo();
1422 // ^^^ impl Fn()
1423 let foo = foo1();
1424 // ^^^ impl Fn(f64)
1425 let foo = foo2();
1426 // ^^^ impl Fn(f64, f64)
1427 let foo = foo3();
1428 // ^^^ impl Fn(f64, f64) -> u32
1429 let foo = foo4();
1430 // ^^^ &dyn Fn(f64, f64) -> u32
1431 let foo = foo5();
1432 // ^^^ &dyn Fn(&dyn Fn(f64, f64) -> u32, f64) -> u32
1433 let foo = foo6();
1434 // ^^^ impl Fn(f64, f64) -> u32 + Sized
1435 let foo = foo7();
1436 // ^^^ *const (impl Fn(f64, f64) -> u32 + Sized)
1437}
1438"#,
1439 )
1440 }
1385} 1441}
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index cea2a13c8..1e03832ec 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -76,8 +76,8 @@ pub use crate::{
76 references::{rename::RenameError, Declaration, ReferenceSearchResult}, 76 references::{rename::RenameError, Declaration, ReferenceSearchResult},
77 runnables::{Runnable, RunnableKind, TestId}, 77 runnables::{Runnable, RunnableKind, TestId},
78 syntax_highlighting::{ 78 syntax_highlighting::{
79 tags::{Highlight, HighlightModifier, HighlightModifiers, HighlightTag}, 79 tags::{Highlight, HlMod, HlMods, HlPunct, HlTag},
80 HighlightedRange, 80 HlRange,
81 }, 81 },
82}; 82};
83pub use assists::{Assist, AssistConfig, AssistId, AssistKind, InsertUseConfig}; 83pub use assists::{Assist, AssistConfig, AssistId, AssistKind, InsertUseConfig};
@@ -92,7 +92,7 @@ pub use ide_db::base_db::{
92}; 92};
93pub use ide_db::{ 93pub use ide_db::{
94 call_info::CallInfo, 94 call_info::CallInfo,
95 search::{Reference, ReferenceAccess, ReferenceKind}, 95 search::{FileReference, ReferenceAccess, ReferenceKind},
96}; 96};
97pub use ide_db::{ 97pub use ide_db::{
98 label::Label, 98 label::Label,
@@ -449,12 +449,12 @@ impl Analysis {
449 } 449 }
450 450
451 /// Computes syntax highlighting for the given file 451 /// Computes syntax highlighting for the given file
452 pub fn highlight(&self, file_id: FileId) -> Cancelable<Vec<HighlightedRange>> { 452 pub fn highlight(&self, file_id: FileId) -> Cancelable<Vec<HlRange>> {
453 self.with_db(|db| syntax_highlighting::highlight(db, file_id, None, false)) 453 self.with_db(|db| syntax_highlighting::highlight(db, file_id, None, false))
454 } 454 }
455 455
456 /// Computes syntax highlighting for the given file range. 456 /// Computes syntax highlighting for the given file range.
457 pub fn highlight_range(&self, frange: FileRange) -> Cancelable<Vec<HighlightedRange>> { 457 pub fn highlight_range(&self, frange: FileRange) -> Cancelable<Vec<HlRange>> {
458 self.with_db(|db| { 458 self.with_db(|db| {
459 syntax_highlighting::highlight(db, frange.file_id, Some(frange.range), false) 459 syntax_highlighting::highlight(db, frange.file_id, Some(frange.range), false)
460 }) 460 })
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs
index c95ed669c..7d4757e02 100644
--- a/crates/ide/src/references.rs
+++ b/crates/ide/src/references.rs
@@ -3,7 +3,7 @@
3//! or `ast::NameRef`. If it's a `ast::NameRef`, at the classification step we 3//! or `ast::NameRef`. If it's a `ast::NameRef`, at the classification step we
4//! try to resolve the direct tree parent of this element, otherwise we 4//! try to resolve the direct tree parent of this element, otherwise we
5//! already have a definition and just need to get its HIR together with 5//! already have a definition and just need to get its HIR together with
6//! some information that is needed for futher steps of searching. 6//! some information that is needed for further steps of searching.
7//! After that, we collect files that might contain references and look 7//! After that, we collect files that might contain references and look
8//! for text occurrences of the identifier. If there's an `ast::NameRef` 8//! for text occurrences of the identifier. If there's an `ast::NameRef`
9//! at the index that the match starts at and its tree parent is 9//! at the index that the match starts at and its tree parent is
@@ -13,15 +13,15 @@ pub(crate) mod rename;
13 13
14use hir::Semantics; 14use hir::Semantics;
15use ide_db::{ 15use ide_db::{
16 base_db::FileId,
16 defs::{Definition, NameClass, NameRefClass}, 17 defs::{Definition, NameClass, NameRefClass},
17 search::Reference, 18 search::{FileReference, ReferenceAccess, ReferenceKind, SearchScope, UsageSearchResult},
18 search::{ReferenceAccess, ReferenceKind, SearchScope},
19 RootDatabase, 19 RootDatabase,
20}; 20};
21use syntax::{ 21use syntax::{
22 algo::find_node_at_offset, 22 algo::find_node_at_offset,
23 ast::{self, NameOwner}, 23 ast::{self, NameOwner},
24 match_ast, AstNode, SyntaxKind, SyntaxNode, TextRange, TokenAtOffset, 24 match_ast, AstNode, SyntaxNode, TextRange, TokenAtOffset, T,
25}; 25};
26 26
27use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeInfo, SymbolKind}; 27use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeInfo, SymbolKind};
@@ -29,7 +29,7 @@ use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeI
29#[derive(Debug, Clone)] 29#[derive(Debug, Clone)]
30pub struct ReferenceSearchResult { 30pub struct ReferenceSearchResult {
31 declaration: Declaration, 31 declaration: Declaration,
32 references: Vec<Reference>, 32 references: UsageSearchResult,
33} 33}
34 34
35#[derive(Debug, Clone)] 35#[derive(Debug, Clone)]
@@ -48,10 +48,21 @@ impl ReferenceSearchResult {
48 &self.declaration.nav 48 &self.declaration.nav
49 } 49 }
50 50
51 pub fn references(&self) -> &[Reference] { 51 pub fn references(&self) -> &UsageSearchResult {
52 &self.references 52 &self.references
53 } 53 }
54 54
55 pub fn references_with_declaration(mut self) -> UsageSearchResult {
56 let decl_ref = FileReference {
57 range: self.declaration.nav.focus_or_full_range(),
58 kind: self.declaration.kind,
59 access: self.declaration.access,
60 };
61 let file_id = self.declaration.nav.file_id;
62 self.references.references.entry(file_id).or_default().push(decl_ref);
63 self.references
64 }
65
55 /// Total number of references 66 /// Total number of references
56 /// At least 1 since all valid references should 67 /// At least 1 since all valid references should
57 /// Have a declaration 68 /// Have a declaration
@@ -63,21 +74,11 @@ impl ReferenceSearchResult {
63// allow turning ReferenceSearchResult into an iterator 74// allow turning ReferenceSearchResult into an iterator
64// over References 75// over References
65impl IntoIterator for ReferenceSearchResult { 76impl IntoIterator for ReferenceSearchResult {
66 type Item = Reference; 77 type Item = (FileId, Vec<FileReference>);
67 type IntoIter = std::vec::IntoIter<Reference>; 78 type IntoIter = std::collections::hash_map::IntoIter<FileId, Vec<FileReference>>;
68 79
69 fn into_iter(mut self) -> Self::IntoIter { 80 fn into_iter(self) -> Self::IntoIter {
70 let mut v = Vec::with_capacity(self.len()); 81 self.references_with_declaration().into_iter()
71 v.push(Reference {
72 file_range: FileRange {
73 file_id: self.declaration.nav.file_id,
74 range: self.declaration.nav.focus_or_full_range(),
75 },
76 kind: self.declaration.kind,
77 access: self.declaration.access,
78 });
79 v.append(&mut self.references);
80 v.into_iter()
81 } 82 }
82} 83}
83 84
@@ -109,13 +110,12 @@ pub(crate) fn find_all_refs(
109 110
110 let RangeInfo { range, info: def } = find_name(&sema, &syntax, position, opt_name)?; 111 let RangeInfo { range, info: def } = find_name(&sema, &syntax, position, opt_name)?;
111 112
112 let references = def 113 let mut usages = def.usages(sema).set_scope(search_scope).all();
113 .usages(sema) 114 usages
114 .set_scope(search_scope) 115 .references
115 .all() 116 .values_mut()
116 .into_iter() 117 .for_each(|it| it.retain(|r| search_kind == ReferenceKind::Other || search_kind == r.kind));
117 .filter(|r| search_kind == ReferenceKind::Other || search_kind == r.kind) 118 usages.references.retain(|_, it| !it.is_empty());
118 .collect();
119 119
120 let nav = def.try_to_nav(sema.db)?; 120 let nav = def.try_to_nav(sema.db)?;
121 let decl_range = nav.focus_or_full_range(); 121 let decl_range = nav.focus_or_full_range();
@@ -130,13 +130,16 @@ pub(crate) fn find_all_refs(
130 kind = ReferenceKind::FieldShorthandForLocal; 130 kind = ReferenceKind::FieldShorthandForLocal;
131 } 131 }
132 } 132 }
133 } else if matches!(def, Definition::LifetimeParam(_) | Definition::Label(_)) { 133 } else if matches!(
134 def,
135 Definition::GenericParam(hir::GenericParam::LifetimeParam(_)) | Definition::Label(_)
136 ) {
134 kind = ReferenceKind::Lifetime; 137 kind = ReferenceKind::Lifetime;
135 }; 138 };
136 139
137 let declaration = Declaration { nav, kind, access: decl_access(&def, &syntax, decl_range) }; 140 let declaration = Declaration { nav, kind, access: decl_access(&def, &syntax, decl_range) };
138 141
139 Some(RangeInfo::new(range, ReferenceSearchResult { declaration, references })) 142 Some(RangeInfo::new(range, ReferenceSearchResult { declaration, references: usages }))
140} 143}
141 144
142fn find_name( 145fn find_name(
@@ -200,7 +203,7 @@ fn get_struct_def_name_for_struct_literal_search(
200 position: FilePosition, 203 position: FilePosition,
201) -> Option<ast::Name> { 204) -> Option<ast::Name> {
202 if let TokenAtOffset::Between(ref left, ref right) = syntax.token_at_offset(position.offset) { 205 if let TokenAtOffset::Between(ref left, ref right) = syntax.token_at_offset(position.offset) {
203 if right.kind() != SyntaxKind::L_CURLY && right.kind() != SyntaxKind::L_PAREN { 206 if right.kind() != T!['{'] && right.kind() != T!['('] {
204 return None; 207 return None;
205 } 208 }
206 if let Some(name) = 209 if let Some(name) =
@@ -227,7 +230,7 @@ fn get_enum_def_name_for_struct_literal_search(
227 position: FilePosition, 230 position: FilePosition,
228) -> Option<ast::Name> { 231) -> Option<ast::Name> {
229 if let TokenAtOffset::Between(ref left, ref right) = syntax.token_at_offset(position.offset) { 232 if let TokenAtOffset::Between(ref left, ref right) = syntax.token_at_offset(position.offset) {
230 if right.kind() != SyntaxKind::L_CURLY && right.kind() != SyntaxKind::L_PAREN { 233 if right.kind() != T!['{'] && right.kind() != T!['('] {
231 return None; 234 return None;
232 } 235 }
233 if let Some(name) = 236 if let Some(name) =
@@ -252,8 +255,8 @@ fn try_find_self_references(
252 syntax: &SyntaxNode, 255 syntax: &SyntaxNode,
253 position: FilePosition, 256 position: FilePosition,
254) -> Option<RangeInfo<ReferenceSearchResult>> { 257) -> Option<RangeInfo<ReferenceSearchResult>> {
255 let self_token = 258 let FilePosition { file_id, offset } = position;
256 syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW)?; 259 let self_token = syntax.token_at_offset(offset).find(|t| t.kind() == T![self])?;
257 let parent = self_token.parent(); 260 let parent = self_token.parent();
258 match_ast! { 261 match_ast! {
259 match parent { 262 match parent {
@@ -274,7 +277,7 @@ fn try_find_self_references(
274 277
275 let declaration = Declaration { 278 let declaration = Declaration {
276 nav: NavigationTarget { 279 nav: NavigationTarget {
277 file_id: position.file_id, 280 file_id,
278 full_range: self_param.syntax().text_range(), 281 full_range: self_param.syntax().text_range(),
279 focus_range: Some(param_self_token.text_range()), 282 focus_range: Some(param_self_token.text_range()),
280 name: param_self_token.text().clone(), 283 name: param_self_token.text().clone(),
@@ -290,7 +293,7 @@ fn try_find_self_references(
290 ReferenceAccess::Read 293 ReferenceAccess::Read
291 }), 294 }),
292 }; 295 };
293 let references = function 296 let refs = function
294 .body() 297 .body()
295 .map(|body| { 298 .map(|body| {
296 body.syntax() 299 body.syntax()
@@ -304,14 +307,16 @@ fn try_find_self_references(
304 None 307 None
305 } 308 }
306 }) 309 })
307 .map(|token| Reference { 310 .map(|token| FileReference {
308 file_range: FileRange { file_id: position.file_id, range: token.text_range() }, 311 range: token.text_range(),
309 kind: ReferenceKind::SelfKw, 312 kind: ReferenceKind::SelfKw,
310 access: declaration.access, // FIXME: properly check access kind here instead of copying it from the declaration 313 access: declaration.access, // FIXME: properly check access kind here instead of copying it from the declaration
311 }) 314 })
312 .collect() 315 .collect()
313 }) 316 })
314 .unwrap_or_default(); 317 .unwrap_or_default();
318 let mut references = UsageSearchResult::default();
319 references.references.insert(file_id, refs);
315 320
316 Some(RangeInfo::new( 321 Some(RangeInfo::new(
317 param_self_token.text_range(), 322 param_self_token.text_range(),
@@ -1016,12 +1021,14 @@ impl Foo {
1016 actual += "\n\n"; 1021 actual += "\n\n";
1017 } 1022 }
1018 1023
1019 for r in &refs.references { 1024 for (file_id, references) in refs.references {
1020 format_to!(actual, "{:?} {:?} {:?}", r.file_range.file_id, r.file_range.range, r.kind); 1025 for r in references {
1021 if let Some(access) = r.access { 1026 format_to!(actual, "{:?} {:?} {:?}", file_id, r.range, r.kind);
1022 format_to!(actual, " {:?}", access); 1027 if let Some(access) = r.access {
1028 format_to!(actual, " {:?}", access);
1029 }
1030 actual += "\n";
1023 } 1031 }
1024 actual += "\n";
1025 } 1032 }
1026 expect.assert_eq(&actual) 1033 expect.assert_eq(&actual)
1027 } 1034 }
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs
index 53d79333c..c3ae568c2 100644
--- a/crates/ide/src/references/rename.rs
+++ b/crates/ide/src/references/rename.rs
@@ -1,29 +1,30 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2use std::{ 2use std::{
3 convert::TryInto, 3 convert::TryInto,
4 error::Error,
5 fmt::{self, Display}, 4 fmt::{self, Display},
6}; 5};
7 6
8use hir::{Module, ModuleDef, ModuleSource, Semantics}; 7use hir::{Module, ModuleDef, ModuleSource, Semantics};
9use ide_db::base_db::{AnchoredPathBuf, FileId, FileRange, SourceDatabaseExt};
10use ide_db::{ 8use ide_db::{
9 base_db::{AnchoredPathBuf, FileId, FileRange, SourceDatabaseExt},
11 defs::{Definition, NameClass, NameRefClass}, 10 defs::{Definition, NameClass, NameRefClass},
11 search::FileReference,
12 RootDatabase, 12 RootDatabase,
13}; 13};
14use syntax::{ 14use syntax::{
15 algo::find_node_at_offset, 15 algo::find_node_at_offset,
16 ast::{self, NameOwner}, 16 ast::{self, NameOwner},
17 lex_single_syntax_kind, match_ast, AstNode, SyntaxKind, SyntaxNode, SyntaxToken, 17 lex_single_syntax_kind, match_ast, AstNode, SyntaxKind, SyntaxNode, SyntaxToken, T,
18}; 18};
19use test_utils::mark; 19use test_utils::mark;
20use text_edit::TextEdit; 20use text_edit::TextEdit;
21 21
22use crate::{ 22use crate::{
23 references::find_all_refs, FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind, 23 FilePosition, FileSystemEdit, RangeInfo, ReferenceKind, ReferenceSearchResult, SourceChange,
24 SourceChange, SourceFileEdit, TextRange, TextSize, 24 SourceFileEdit, TextRange, TextSize,
25}; 25};
26 26
27type RenameResult<T> = Result<T, RenameError>;
27#[derive(Debug)] 28#[derive(Debug)]
28pub struct RenameError(pub(crate) String); 29pub struct RenameError(pub(crate) String);
29 30
@@ -33,26 +34,30 @@ impl fmt::Display for RenameError {
33 } 34 }
34} 35}
35 36
36impl Error for RenameError {} 37macro_rules! format_err {
38 ($fmt:expr) => {RenameError(format!($fmt))};
39 ($fmt:expr, $($arg:tt)+) => {RenameError(format!($fmt, $($arg)+))}
40}
41
42macro_rules! bail {
43 ($($tokens:tt)*) => {return Err(format_err!($($tokens)*))}
44}
37 45
38pub(crate) fn prepare_rename( 46pub(crate) fn prepare_rename(
39 db: &RootDatabase, 47 db: &RootDatabase,
40 position: FilePosition, 48 position: FilePosition,
41) -> Result<RangeInfo<()>, RenameError> { 49) -> RenameResult<RangeInfo<()>> {
42 let sema = Semantics::new(db); 50 let sema = Semantics::new(db);
43 let source_file = sema.parse(position.file_id); 51 let source_file = sema.parse(position.file_id);
44 let syntax = source_file.syntax(); 52 let syntax = source_file.syntax();
45 if let Some(module) = find_module_at_offset(&sema, position, syntax) { 53 if let Some(module) = find_module_at_offset(&sema, position, syntax) {
46 rename_mod(&sema, position, module, "dummy") 54 rename_mod(&sema, position, module, "dummy")
47 } else if let Some(self_token) = 55 } else if let Some(self_token) =
48 syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW) 56 syntax.token_at_offset(position.offset).find(|t| t.kind() == T![self])
49 { 57 {
50 rename_self_to_param(&sema, position, self_token, "dummy") 58 rename_self_to_param(&sema, position, self_token, "dummy")
51 } else { 59 } else {
52 let range = match find_all_refs(&sema, position, None) { 60 let RangeInfo { range, .. } = find_all_refs(&sema, position)?;
53 Some(RangeInfo { range, .. }) => range,
54 None => return Err(RenameError("No references found at position".to_string())),
55 };
56 Ok(RangeInfo::new(range, SourceChange::from(vec![]))) 61 Ok(RangeInfo::new(range, SourceChange::from(vec![])))
57 } 62 }
58 .map(|info| RangeInfo::new(info.range, ())) 63 .map(|info| RangeInfo::new(info.range, ()))
@@ -62,7 +67,7 @@ pub(crate) fn rename(
62 db: &RootDatabase, 67 db: &RootDatabase,
63 position: FilePosition, 68 position: FilePosition,
64 new_name: &str, 69 new_name: &str,
65) -> Result<RangeInfo<SourceChange>, RenameError> { 70) -> RenameResult<RangeInfo<SourceChange>> {
66 let sema = Semantics::new(db); 71 let sema = Semantics::new(db);
67 rename_with_semantics(&sema, position, new_name) 72 rename_with_semantics(&sema, position, new_name)
68} 73}
@@ -71,42 +76,18 @@ pub(crate) fn rename_with_semantics(
71 sema: &Semantics<RootDatabase>, 76 sema: &Semantics<RootDatabase>,
72 position: FilePosition, 77 position: FilePosition,
73 new_name: &str, 78 new_name: &str,
74) -> Result<RangeInfo<SourceChange>, RenameError> { 79) -> RenameResult<RangeInfo<SourceChange>> {
75 let is_lifetime_name = match lex_single_syntax_kind(new_name) {
76 Some(res) => match res {
77 (SyntaxKind::IDENT, _) => false,
78 (SyntaxKind::UNDERSCORE, _) => false,
79 (SyntaxKind::SELF_KW, _) => return rename_to_self(&sema, position),
80 (SyntaxKind::LIFETIME_IDENT, _) if new_name != "'static" && new_name != "'_" => true,
81 (SyntaxKind::LIFETIME_IDENT, _) => {
82 return Err(RenameError(format!(
83 "Invalid name `{0}`: Cannot rename lifetime to {0}",
84 new_name
85 )))
86 }
87 (_, Some(syntax_error)) => {
88 return Err(RenameError(format!("Invalid name `{}`: {}", new_name, syntax_error)))
89 }
90 (_, None) => {
91 return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name)))
92 }
93 },
94 None => return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name))),
95 };
96
97 let source_file = sema.parse(position.file_id); 80 let source_file = sema.parse(position.file_id);
98 let syntax = source_file.syntax(); 81 let syntax = source_file.syntax();
99 // this is here to prevent lifetime renames from happening on modules and self 82
100 if is_lifetime_name { 83 if let Some(module) = find_module_at_offset(&sema, position, syntax) {
101 rename_reference(&sema, position, new_name, is_lifetime_name)
102 } else if let Some(module) = find_module_at_offset(&sema, position, syntax) {
103 rename_mod(&sema, position, module, new_name) 84 rename_mod(&sema, position, module, new_name)
104 } else if let Some(self_token) = 85 } else if let Some(self_token) =
105 syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW) 86 syntax.token_at_offset(position.offset).find(|t| t.kind() == T![self])
106 { 87 {
107 rename_self_to_param(&sema, position, self_token, new_name) 88 rename_self_to_param(&sema, position, self_token, new_name)
108 } else { 89 } else {
109 rename_reference(&sema, position, new_name, is_lifetime_name) 90 rename_reference(&sema, position, new_name)
110 } 91 }
111} 92}
112 93
@@ -127,6 +108,33 @@ pub(crate) fn will_rename_file(
127 Some(change) 108 Some(change)
128} 109}
129 110
111#[derive(Debug, PartialEq)]
112enum IdentifierKind {
113 Ident,
114 Lifetime,
115 ToSelf,
116 Underscore,
117}
118
119fn check_identifier(new_name: &str) -> RenameResult<IdentifierKind> {
120 match lex_single_syntax_kind(new_name) {
121 Some(res) => match res {
122 (SyntaxKind::IDENT, _) => Ok(IdentifierKind::Ident),
123 (T![_], _) => Ok(IdentifierKind::Underscore),
124 (T![self], _) => Ok(IdentifierKind::ToSelf),
125 (SyntaxKind::LIFETIME_IDENT, _) if new_name != "'static" && new_name != "'_" => {
126 Ok(IdentifierKind::Lifetime)
127 }
128 (SyntaxKind::LIFETIME_IDENT, _) => {
129 bail!("Invalid name `{0}`: Cannot rename lifetime to {0}", new_name)
130 }
131 (_, Some(syntax_error)) => bail!("Invalid name `{}`: {}", new_name, syntax_error),
132 (_, None) => bail!("Invalid name `{}`: not an identifier", new_name),
133 },
134 None => bail!("Invalid name `{}`: not an identifier", new_name),
135 }
136}
137
130fn find_module_at_offset( 138fn find_module_at_offset(
131 sema: &Semantics<RootDatabase>, 139 sema: &Semantics<RootDatabase>,
132 position: FilePosition, 140 position: FilePosition,
@@ -155,39 +163,54 @@ fn find_module_at_offset(
155 Some(module) 163 Some(module)
156} 164}
157 165
158fn source_edit_from_reference( 166fn find_all_refs(
167 sema: &Semantics<RootDatabase>,
168 position: FilePosition,
169) -> RenameResult<RangeInfo<ReferenceSearchResult>> {
170 crate::references::find_all_refs(sema, position, None)
171 .ok_or_else(|| format_err!("No references found at position"))
172}
173
174fn source_edit_from_references(
159 sema: &Semantics<RootDatabase>, 175 sema: &Semantics<RootDatabase>,
160 reference: Reference, 176 file_id: FileId,
177 references: &[FileReference],
161 new_name: &str, 178 new_name: &str,
162) -> SourceFileEdit { 179) -> SourceFileEdit {
163 let mut replacement_text = String::new(); 180 let mut edit = TextEdit::builder();
164 let range = match reference.kind { 181 for reference in references {
165 ReferenceKind::FieldShorthandForField => { 182 let mut replacement_text = String::new();
166 mark::hit!(test_rename_struct_field_for_shorthand); 183 let range = match reference.kind {
167 replacement_text.push_str(new_name); 184 ReferenceKind::FieldShorthandForField => {
168 replacement_text.push_str(": "); 185 mark::hit!(test_rename_struct_field_for_shorthand);
169 TextRange::new(reference.file_range.range.start(), reference.file_range.range.start()) 186 replacement_text.push_str(new_name);
170 } 187 replacement_text.push_str(": ");
171 ReferenceKind::FieldShorthandForLocal => { 188 TextRange::new(reference.range.start(), reference.range.start())
172 mark::hit!(test_rename_local_for_field_shorthand); 189 }
173 replacement_text.push_str(": "); 190 ReferenceKind::FieldShorthandForLocal => {
174 replacement_text.push_str(new_name); 191 mark::hit!(test_rename_local_for_field_shorthand);
175 TextRange::new(reference.file_range.range.end(), reference.file_range.range.end()) 192 replacement_text.push_str(": ");
176 } 193 replacement_text.push_str(new_name);
177 ReferenceKind::RecordFieldExprOrPat => { 194 TextRange::new(reference.range.end(), reference.range.end())
178 mark::hit!(test_rename_field_expr_pat); 195 }
179 replacement_text.push_str(new_name); 196 ReferenceKind::RecordFieldExprOrPat => {
180 edit_text_range_for_record_field_expr_or_pat(sema, reference.file_range, new_name) 197 mark::hit!(test_rename_field_expr_pat);
181 } 198 replacement_text.push_str(new_name);
182 _ => { 199 edit_text_range_for_record_field_expr_or_pat(
183 replacement_text.push_str(new_name); 200 sema,
184 reference.file_range.range 201 FileRange { file_id, range: reference.range },
185 } 202 new_name,
186 }; 203 )
187 SourceFileEdit { 204 }
188 file_id: reference.file_range.file_id, 205 _ => {
189 edit: TextEdit::replace(range, replacement_text), 206 replacement_text.push_str(new_name);
207 reference.range
208 }
209 };
210 edit.replace(range, replacement_text);
190 } 211 }
212
213 SourceFileEdit { file_id, edit: edit.finish() }
191} 214}
192 215
193fn edit_text_range_for_record_field_expr_or_pat( 216fn edit_text_range_for_record_field_expr_or_pat(
@@ -223,7 +246,10 @@ fn rename_mod(
223 position: FilePosition, 246 position: FilePosition,
224 module: Module, 247 module: Module,
225 new_name: &str, 248 new_name: &str,
226) -> Result<RangeInfo<SourceChange>, RenameError> { 249) -> RenameResult<RangeInfo<SourceChange>> {
250 if IdentifierKind::Ident != check_identifier(new_name)? {
251 bail!("Invalid name `{0}`: cannot rename module to {0}", new_name);
252 }
227 let mut source_file_edits = Vec::new(); 253 let mut source_file_edits = Vec::new();
228 let mut file_system_edits = Vec::new(); 254 let mut file_system_edits = Vec::new();
229 255
@@ -254,12 +280,10 @@ fn rename_mod(
254 source_file_edits.push(edit); 280 source_file_edits.push(edit);
255 } 281 }
256 282
257 let RangeInfo { range, info: refs } = find_all_refs(sema, position, None) 283 let RangeInfo { range, info: refs } = find_all_refs(sema, position)?;
258 .ok_or_else(|| RenameError("No references found at position".to_string()))?; 284 let ref_edits = refs.references().iter().map(|(&file_id, references)| {
259 let ref_edits = refs 285 source_edit_from_references(sema, file_id, references, new_name)
260 .references 286 });
261 .into_iter()
262 .map(|reference| source_edit_from_reference(sema, reference, new_name));
263 source_file_edits.extend(ref_edits); 287 source_file_edits.extend(ref_edits);
264 288
265 Ok(RangeInfo::new(range, SourceChange::from_edits(source_file_edits, file_system_edits))) 289 Ok(RangeInfo::new(range, SourceChange::from_edits(source_file_edits, file_system_edits)))
@@ -274,27 +298,26 @@ fn rename_to_self(
274 298
275 let (fn_def, fn_ast) = find_node_at_offset::<ast::Fn>(syn, position.offset) 299 let (fn_def, fn_ast) = find_node_at_offset::<ast::Fn>(syn, position.offset)
276 .and_then(|fn_ast| sema.to_def(&fn_ast).zip(Some(fn_ast))) 300 .and_then(|fn_ast| sema.to_def(&fn_ast).zip(Some(fn_ast)))
277 .ok_or_else(|| RenameError("No surrounding method declaration found".to_string()))?; 301 .ok_or_else(|| format_err!("No surrounding method declaration found"))?;
278 let param_range = fn_ast 302 let param_range = fn_ast
279 .param_list() 303 .param_list()
280 .and_then(|p| p.params().next()) 304 .and_then(|p| p.params().next())
281 .ok_or_else(|| RenameError("Method has no parameters".to_string()))? 305 .ok_or_else(|| format_err!("Method has no parameters"))?
282 .syntax() 306 .syntax()
283 .text_range(); 307 .text_range();
284 if !param_range.contains(position.offset) { 308 if !param_range.contains(position.offset) {
285 return Err(RenameError("Only the first parameter can be self".to_string())); 309 bail!("Only the first parameter can be self");
286 } 310 }
287 311
288 let impl_block = find_node_at_offset::<ast::Impl>(syn, position.offset) 312 let impl_block = find_node_at_offset::<ast::Impl>(syn, position.offset)
289 .and_then(|def| sema.to_def(&def)) 313 .and_then(|def| sema.to_def(&def))
290 .ok_or_else(|| RenameError("No impl block found for function".to_string()))?; 314 .ok_or_else(|| format_err!("No impl block found for function"))?;
291 if fn_def.self_param(sema.db).is_some() { 315 if fn_def.self_param(sema.db).is_some() {
292 return Err(RenameError("Method already has a self parameter".to_string())); 316 bail!("Method already has a self parameter");
293 } 317 }
294 318
295 let params = fn_def.assoc_fn_params(sema.db); 319 let params = fn_def.assoc_fn_params(sema.db);
296 let first_param = 320 let first_param = params.first().ok_or_else(|| format_err!("Method has no parameters"))?;
297 params.first().ok_or_else(|| RenameError("Method has no parameters".into()))?;
298 let first_param_ty = first_param.ty(); 321 let first_param_ty = first_param.ty();
299 let impl_ty = impl_block.target_ty(sema.db); 322 let impl_ty = impl_block.target_ty(sema.db);
300 let (ty, self_param) = if impl_ty.remove_ref().is_some() { 323 let (ty, self_param) = if impl_ty.remove_ref().is_some() {
@@ -307,23 +330,17 @@ fn rename_to_self(
307 }; 330 };
308 331
309 if ty != impl_ty { 332 if ty != impl_ty {
310 return Err(RenameError("Parameter type differs from impl block type".to_string())); 333 bail!("Parameter type differs from impl block type");
311 } 334 }
312 335
313 let RangeInfo { range, info: refs } = find_all_refs(sema, position, None) 336 let RangeInfo { range, info: refs } = find_all_refs(sema, position)?;
314 .ok_or_else(|| RenameError("No reference found at position".to_string()))?;
315
316 let (param_ref, usages): (Vec<Reference>, Vec<Reference>) = refs
317 .into_iter()
318 .partition(|reference| param_range.intersect(reference.file_range.range).is_some());
319
320 if param_ref.is_empty() {
321 return Err(RenameError("Parameter to rename not found".to_string()));
322 }
323 337
324 let mut edits = usages 338 let mut edits = refs
325 .into_iter() 339 .references()
326 .map(|reference| source_edit_from_reference(sema, reference, "self")) 340 .iter()
341 .map(|(&file_id, references)| {
342 source_edit_from_references(sema, file_id, references, "self")
343 })
327 .collect::<Vec<_>>(); 344 .collect::<Vec<_>>();
328 345
329 edits.push(SourceFileEdit { 346 edits.push(SourceFileEdit {
@@ -367,12 +384,22 @@ fn rename_self_to_param(
367 self_token: SyntaxToken, 384 self_token: SyntaxToken,
368 new_name: &str, 385 new_name: &str,
369) -> Result<RangeInfo<SourceChange>, RenameError> { 386) -> Result<RangeInfo<SourceChange>, RenameError> {
387 let ident_kind = check_identifier(new_name)?;
388 match ident_kind {
389 IdentifierKind::Lifetime => bail!("Invalid name `{}`: not an identifier", new_name),
390 IdentifierKind::ToSelf => {
391 // no-op
392 mark::hit!(rename_self_to_self);
393 return Ok(RangeInfo::new(self_token.text_range(), SourceChange::default()));
394 }
395 _ => (),
396 }
370 let source_file = sema.parse(position.file_id); 397 let source_file = sema.parse(position.file_id);
371 let syn = source_file.syntax(); 398 let syn = source_file.syntax();
372 399
373 let text = sema.db.file_text(position.file_id); 400 let text = sema.db.file_text(position.file_id);
374 let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset) 401 let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset)
375 .ok_or_else(|| RenameError("No surrounding method declaration found".to_string()))?; 402 .ok_or_else(|| format_err!("No surrounding method declaration found"))?;
376 let search_range = fn_def.syntax().text_range(); 403 let search_range = fn_def.syntax().text_range();
377 404
378 let mut edits: Vec<SourceFileEdit> = vec![]; 405 let mut edits: Vec<SourceFileEdit> = vec![];
@@ -382,12 +409,10 @@ fn rename_self_to_param(
382 if !search_range.contains_inclusive(offset) { 409 if !search_range.contains_inclusive(offset) {
383 continue; 410 continue;
384 } 411 }
385 if let Some(ref usage) = 412 if let Some(ref usage) = syn.token_at_offset(offset).find(|t| t.kind() == T![self]) {
386 syn.token_at_offset(offset).find(|t| t.kind() == SyntaxKind::SELF_KW)
387 {
388 let edit = if let Some(ref self_param) = ast::SelfParam::cast(usage.parent()) { 413 let edit = if let Some(ref self_param) = ast::SelfParam::cast(usage.parent()) {
389 text_edit_from_self_param(syn, self_param, new_name) 414 text_edit_from_self_param(syn, self_param, new_name)
390 .ok_or_else(|| RenameError("No target type found".to_string()))? 415 .ok_or_else(|| format_err!("No target type found"))?
391 } else { 416 } else {
392 TextEdit::replace(usage.text_range(), String::from(new_name)) 417 TextEdit::replace(usage.text_range(), String::from(new_name))
393 }; 418 };
@@ -395,6 +420,10 @@ fn rename_self_to_param(
395 } 420 }
396 } 421 }
397 422
423 if edits.len() > 1 && ident_kind == IdentifierKind::Underscore {
424 bail!("Cannot rename reference to `_` as it is being referenced multiple times");
425 }
426
398 let range = ast::SelfParam::cast(self_token.parent()) 427 let range = ast::SelfParam::cast(self_token.parent())
399 .map_or(self_token.text_range(), |p| p.syntax().text_range()); 428 .map_or(self_token.text_range(), |p| p.syntax().text_range());
400 429
@@ -405,35 +434,43 @@ fn rename_reference(
405 sema: &Semantics<RootDatabase>, 434 sema: &Semantics<RootDatabase>,
406 position: FilePosition, 435 position: FilePosition,
407 new_name: &str, 436 new_name: &str,
408 is_lifetime_name: bool,
409) -> Result<RangeInfo<SourceChange>, RenameError> { 437) -> Result<RangeInfo<SourceChange>, RenameError> {
410 let RangeInfo { range, info: refs } = match find_all_refs(sema, position, None) { 438 let ident_kind = check_identifier(new_name)?;
411 Some(range_info) => range_info, 439 let RangeInfo { range, info: refs } = find_all_refs(sema, position)?;
412 None => return Err(RenameError("No references found at position".to_string())), 440
413 }; 441 match (ident_kind, &refs.declaration.kind) {
414 442 (IdentifierKind::ToSelf, ReferenceKind::Lifetime)
415 match (refs.declaration.kind == ReferenceKind::Lifetime, is_lifetime_name) { 443 | (IdentifierKind::Underscore, ReferenceKind::Lifetime)
416 (true, false) => { 444 | (IdentifierKind::Ident, ReferenceKind::Lifetime) => {
417 return Err(RenameError(format!( 445 mark::hit!(rename_not_a_lifetime_ident_ref);
418 "Invalid name `{}`: not a lifetime identifier", 446 bail!("Invalid name `{}`: not a lifetime identifier", new_name)
419 new_name
420 )))
421 } 447 }
422 (false, true) => { 448 (IdentifierKind::Lifetime, ReferenceKind::Lifetime) => mark::hit!(rename_lifetime),
423 return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name))) 449 (IdentifierKind::Lifetime, _) => {
450 mark::hit!(rename_not_an_ident_ref);
451 bail!("Invalid name `{}`: not an identifier", new_name)
424 } 452 }
425 _ => (), 453 (IdentifierKind::ToSelf, ReferenceKind::SelfKw) => {
454 unreachable!("rename_self_to_param should've been called instead")
455 }
456 (IdentifierKind::ToSelf, _) => {
457 mark::hit!(rename_to_self);
458 return rename_to_self(sema, position);
459 }
460 (IdentifierKind::Underscore, _) if !refs.references.is_empty() => {
461 mark::hit!(rename_underscore_multiple);
462 bail!("Cannot rename reference to `_` as it is being referenced multiple times")
463 }
464 (IdentifierKind::Ident, _) | (IdentifierKind::Underscore, _) => mark::hit!(rename_ident),
426 } 465 }
427 466
428 let edit = refs 467 let edit = refs
429 .into_iter() 468 .into_iter()
430 .map(|reference| source_edit_from_reference(sema, reference, new_name)) 469 .map(|(file_id, references)| {
470 source_edit_from_references(sema, file_id, &references, new_name)
471 })
431 .collect::<Vec<_>>(); 472 .collect::<Vec<_>>();
432 473
433 if edit.is_empty() {
434 return Err(RenameError("No references found at position".to_string()));
435 }
436
437 Ok(RangeInfo::new(range, SourceChange::from(edit))) 474 Ok(RangeInfo::new(range, SourceChange::from(edit)))
438} 475}
439 476
@@ -462,9 +499,11 @@ mod tests {
462 text_edit_builder.replace(indel.delete, indel.insert); 499 text_edit_builder.replace(indel.delete, indel.insert);
463 } 500 }
464 } 501 }
465 let mut result = analysis.file_text(file_id.unwrap()).unwrap().to_string(); 502 if let Some(file_id) = file_id {
466 text_edit_builder.finish().apply(&mut result); 503 let mut result = analysis.file_text(file_id).unwrap().to_string();
467 assert_eq_text!(ra_fixture_after, &*result); 504 text_edit_builder.finish().apply(&mut result);
505 assert_eq_text!(ra_fixture_after, &*result);
506 }
468 } 507 }
469 Err(err) => { 508 Err(err) => {
470 if ra_fixture_after.starts_with("error:") { 509 if ra_fixture_after.starts_with("error:") {
@@ -530,6 +569,7 @@ mod tests {
530 569
531 #[test] 570 #[test]
532 fn test_rename_to_invalid_identifier_lifetime() { 571 fn test_rename_to_invalid_identifier_lifetime() {
572 mark::check!(rename_not_an_ident_ref);
533 check( 573 check(
534 "'foo", 574 "'foo",
535 r#"fn main() { let i$0 = 1; }"#, 575 r#"fn main() { let i$0 = 1; }"#,
@@ -539,6 +579,7 @@ mod tests {
539 579
540 #[test] 580 #[test]
541 fn test_rename_to_invalid_identifier_lifetime2() { 581 fn test_rename_to_invalid_identifier_lifetime2() {
582 mark::check!(rename_not_a_lifetime_ident_ref);
542 check( 583 check(
543 "foo", 584 "foo",
544 r#"fn main<'a>(_: &'a$0 ()) {}"#, 585 r#"fn main<'a>(_: &'a$0 ()) {}"#,
@@ -547,7 +588,27 @@ mod tests {
547 } 588 }
548 589
549 #[test] 590 #[test]
591 fn test_rename_to_underscore_invalid() {
592 mark::check!(rename_underscore_multiple);
593 check(
594 "_",
595 r#"fn main(foo$0: ()) {foo;}"#,
596 "error: Cannot rename reference to `_` as it is being referenced multiple times",
597 );
598 }
599
600 #[test]
601 fn test_rename_mod_invalid() {
602 check(
603 "'foo",
604 r#"mod foo$0 {}"#,
605 "error: Invalid name `'foo`: cannot rename module to 'foo",
606 );
607 }
608
609 #[test]
550 fn test_rename_for_local() { 610 fn test_rename_for_local() {
611 mark::check!(rename_ident);
551 check( 612 check(
552 "k", 613 "k",
553 r#" 614 r#"
@@ -945,7 +1006,7 @@ use crate::foo$0::FooContent;
945//- /lib.rs 1006//- /lib.rs
946mod fo$0o; 1007mod fo$0o;
947//- /foo/mod.rs 1008//- /foo/mod.rs
948// emtpy 1009// empty
949"#, 1010"#,
950 expect![[r#" 1011 expect![[r#"
951 RangeInfo { 1012 RangeInfo {
@@ -995,7 +1056,7 @@ mod fo$0o;
995mod outer { mod fo$0o; } 1056mod outer { mod fo$0o; }
996 1057
997//- /outer/foo.rs 1058//- /outer/foo.rs
998// emtpy 1059// empty
999"#, 1060"#,
1000 expect![[r#" 1061 expect![[r#"
1001 RangeInfo { 1062 RangeInfo {
@@ -1178,6 +1239,7 @@ fn foo(f: foo::Foo) {
1178 1239
1179 #[test] 1240 #[test]
1180 fn test_parameter_to_self() { 1241 fn test_parameter_to_self() {
1242 mark::check!(rename_to_self);
1181 check( 1243 check(
1182 "self", 1244 "self",
1183 r#" 1245 r#"
@@ -1481,6 +1543,7 @@ fn foo(Foo { i: bar }: foo) -> i32 {
1481 1543
1482 #[test] 1544 #[test]
1483 fn test_rename_lifetimes() { 1545 fn test_rename_lifetimes() {
1546 mark::check!(rename_lifetime);
1484 check( 1547 check(
1485 "'yeeee", 1548 "'yeeee",
1486 r#" 1549 r#"
@@ -1565,4 +1628,24 @@ fn foo<'a>() -> &'a () {
1565"#, 1628"#,
1566 ) 1629 )
1567 } 1630 }
1631
1632 #[test]
1633 fn test_self_to_self() {
1634 mark::check!(rename_self_to_self);
1635 check(
1636 "self",
1637 r#"
1638struct Foo;
1639impl Foo {
1640 fn foo(self$0) {}
1641}
1642"#,
1643 r#"
1644struct Foo;
1645impl Foo {
1646 fn foo(self) {}
1647}
1648"#,
1649 )
1650 }
1568} 1651}
diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs
index 557563d7e..f5ee7de86 100644
--- a/crates/ide/src/runnables.rs
+++ b/crates/ide/src/runnables.rs
@@ -3,7 +3,7 @@ use std::fmt;
3use assists::utils::test_related_attribute; 3use assists::utils::test_related_attribute;
4use cfg::CfgExpr; 4use cfg::CfgExpr;
5use hir::{AsAssocItem, HasAttrs, HasSource, Semantics}; 5use hir::{AsAssocItem, HasAttrs, HasSource, Semantics};
6use ide_db::RootDatabase; 6use ide_db::{defs::Definition, RootDatabase};
7use itertools::Itertools; 7use itertools::Itertools;
8use syntax::{ 8use syntax::{
9 ast::{self, AstNode, AttrsOwner, ModuleItemOwner}, 9 ast::{self, AstNode, AttrsOwner, ModuleItemOwner},
@@ -96,21 +96,26 @@ impl Runnable {
96pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> { 96pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {
97 let sema = Semantics::new(db); 97 let sema = Semantics::new(db);
98 let source_file = sema.parse(file_id); 98 let source_file = sema.parse(file_id);
99 source_file.syntax().descendants().filter_map(|i| runnable(&sema, i)).collect() 99 source_file
100} 100 .syntax()
101 101 .descendants()
102pub(crate) fn runnable(sema: &Semantics<RootDatabase>, item: SyntaxNode) -> Option<Runnable> { 102 .filter_map(|item| {
103 let runnable_item = match_ast! { 103 let runnable = match_ast! {
104 match (item.clone()) { 104 match item {
105 ast::Fn(func) => { 105 ast::Fn(func) => {
106 let def = sema.to_def(&func)?; 106 let def = sema.to_def(&func)?;
107 runnable_fn(sema, def) 107 runnable_fn(&sema, def)
108 }, 108 },
109 ast::Module(it) => runnable_mod(sema, it), 109 ast::Module(it) => runnable_mod(&sema, it),
110 _ => None, 110 _ => None,
111 } 111 }
112 }; 112 };
113 runnable_item.or_else(|| runnable_doctest(sema, item)) 113 runnable.or_else(|| match doc_owner_to_def(&sema, item)? {
114 Definition::ModuleDef(def) => module_def_doctest(&sema, def),
115 _ => None,
116 })
117 })
118 .collect()
114} 119}
115 120
116pub(crate) fn runnable_fn(sema: &Semantics<RootDatabase>, def: hir::Function) -> Option<Runnable> { 121pub(crate) fn runnable_fn(sema: &Semantics<RootDatabase>, def: hir::Function) -> Option<Runnable> {
@@ -145,20 +150,49 @@ pub(crate) fn runnable_fn(sema: &Semantics<RootDatabase>, def: hir::Function) ->
145 Some(Runnable { nav, kind, cfg }) 150 Some(Runnable { nav, kind, cfg })
146} 151}
147 152
148fn runnable_doctest(sema: &Semantics<RootDatabase>, item: SyntaxNode) -> Option<Runnable> { 153pub(crate) fn runnable_mod(
149 match_ast! { 154 sema: &Semantics<RootDatabase>,
155 module: ast::Module,
156) -> Option<Runnable> {
157 if !has_test_function_or_multiple_test_submodules(&module) {
158 return None;
159 }
160 let module_def = sema.to_def(&module)?;
161
162 let path = module_def
163 .path_to_root(sema.db)
164 .into_iter()
165 .rev()
166 .filter_map(|it| it.name(sema.db))
167 .join("::");
168
169 let def = sema.to_def(&module)?;
170 let attrs = def.attrs(sema.db);
171 let cfg = attrs.cfg();
172 let nav = module_def.to_nav(sema.db);
173 Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg })
174}
175
176// FIXME: figure out a proper API here.
177pub(crate) fn doc_owner_to_def(
178 sema: &Semantics<RootDatabase>,
179 item: SyntaxNode,
180) -> Option<Definition> {
181 let res: hir::ModuleDef = match_ast! {
150 match item { 182 match item {
151 ast::Fn(it) => module_def_doctest(sema, sema.to_def(&it)?.into()), 183 ast::SourceFile(it) => sema.scope(&item).module()?.into(),
152 ast::Struct(it) => module_def_doctest(sema, sema.to_def(&it)?.into()), 184 ast::Fn(it) => sema.to_def(&it)?.into(),
153 ast::Enum(it) => module_def_doctest(sema, sema.to_def(&it)?.into()), 185 ast::Struct(it) => sema.to_def(&it)?.into(),
154 ast::Union(it) => module_def_doctest(sema, sema.to_def(&it)?.into()), 186 ast::Enum(it) => sema.to_def(&it)?.into(),
155 ast::Trait(it) => module_def_doctest(sema, sema.to_def(&it)?.into()), 187 ast::Union(it) => sema.to_def(&it)?.into(),
156 ast::Const(it) => module_def_doctest(sema, sema.to_def(&it)?.into()), 188 ast::Trait(it) => sema.to_def(&it)?.into(),
157 ast::Static(it) => module_def_doctest(sema, sema.to_def(&it)?.into()), 189 ast::Const(it) => sema.to_def(&it)?.into(),
158 ast::TypeAlias(it) => module_def_doctest(sema, sema.to_def(&it)?.into()), 190 ast::Static(it) => sema.to_def(&it)?.into(),
159 _ => None, 191 ast::TypeAlias(it) => sema.to_def(&it)?.into(),
192 _ => return None,
160 } 193 }
161 } 194 };
195 Some(Definition::ModuleDef(res))
162} 196}
163 197
164fn module_def_doctest(sema: &Semantics<RootDatabase>, def: hir::ModuleDef) -> Option<Runnable> { 198fn module_def_doctest(sema: &Semantics<RootDatabase>, def: hir::ModuleDef) -> Option<Runnable> {
@@ -253,26 +287,6 @@ fn has_runnable_doc_test(attrs: &hir::Attrs) -> bool {
253 }) 287 })
254} 288}
255 289
256fn runnable_mod(sema: &Semantics<RootDatabase>, module: ast::Module) -> Option<Runnable> {
257 if !has_test_function_or_multiple_test_submodules(&module) {
258 return None;
259 }
260 let module_def = sema.to_def(&module)?;
261
262 let path = module_def
263 .path_to_root(sema.db)
264 .into_iter()
265 .rev()
266 .filter_map(|it| it.name(sema.db))
267 .join("::");
268
269 let def = sema.to_def(&module)?;
270 let attrs = def.attrs(sema.db);
271 let cfg = attrs.cfg();
272 let nav = module_def.to_nav(sema.db);
273 Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg })
274}
275
276// We could create runnables for modules with number_of_test_submodules > 0, 290// We could create runnables for modules with number_of_test_submodules > 0,
277// but that bloats the runnables for no real benefit, since all tests can be run by the submodule already 291// but that bloats the runnables for no real benefit, since all tests can be run by the submodule already
278fn has_test_function_or_multiple_test_submodules(module: &ast::Module) -> bool { 292fn has_test_function_or_multiple_test_submodules(module: &ast::Module) -> bool {
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs
index ba0085244..f2d4da78d 100644
--- a/crates/ide/src/syntax_highlighting.rs
+++ b/crates/ide/src/syntax_highlighting.rs
@@ -1,35 +1,39 @@
1pub(crate) mod tags;
2
3mod highlights;
4mod injector;
5
6mod highlight;
1mod format; 7mod format;
2mod html;
3mod injection;
4mod macro_rules; 8mod macro_rules;
5pub(crate) mod tags; 9mod inject;
10
11mod html;
6#[cfg(test)] 12#[cfg(test)]
7mod tests; 13mod tests;
8 14
9use hir::{AsAssocItem, Local, Name, Semantics, VariantDef}; 15use hir::{Name, Semantics};
10use ide_db::{ 16use ide_db::RootDatabase;
11 defs::{Definition, NameClass, NameRefClass},
12 RootDatabase,
13};
14use rustc_hash::FxHashMap; 17use rustc_hash::FxHashMap;
15use syntax::{ 18use syntax::{
16 ast::{self, HasFormatSpecifier}, 19 ast::{self, HasFormatSpecifier},
17 AstNode, AstToken, Direction, NodeOrToken, SyntaxElement, 20 AstNode, AstToken, Direction, NodeOrToken,
18 SyntaxKind::{self, *}, 21 SyntaxKind::*,
19 SyntaxNode, SyntaxToken, TextRange, WalkEvent, T, 22 SyntaxNode, TextRange, WalkEvent, T,
20}; 23};
21 24
22use crate::{ 25use crate::{
23 syntax_highlighting::{ 26 syntax_highlighting::{
24 format::FormatStringHighlighter, macro_rules::MacroRulesHighlighter, tags::Highlight, 27 format::highlight_format_string, highlights::Highlights,
28 macro_rules::MacroRulesHighlighter, tags::Highlight,
25 }, 29 },
26 FileId, HighlightModifier, HighlightTag, SymbolKind, 30 FileId, HlMod, HlTag, SymbolKind,
27}; 31};
28 32
29pub(crate) use html::highlight_as_html; 33pub(crate) use html::highlight_as_html;
30 34
31#[derive(Debug, Clone)] 35#[derive(Debug, Clone, Copy)]
32pub struct HighlightedRange { 36pub struct HlRange {
33 pub range: TextRange, 37 pub range: TextRange,
34 pub highlight: Highlight, 38 pub highlight: Highlight,
35 pub binding_hash: Option<u64>, 39 pub binding_hash: Option<u64>,
@@ -49,7 +53,7 @@ pub(crate) fn highlight(
49 file_id: FileId, 53 file_id: FileId,
50 range_to_highlight: Option<TextRange>, 54 range_to_highlight: Option<TextRange>,
51 syntactic_name_ref_highlighting: bool, 55 syntactic_name_ref_highlighting: bool,
52) -> Vec<HighlightedRange> { 56) -> Vec<HlRange> {
53 let _p = profile::span("highlight"); 57 let _p = profile::span("highlight");
54 let sema = Semantics::new(db); 58 let sema = Semantics::new(db);
55 59
@@ -68,28 +72,30 @@ pub(crate) fn highlight(
68 } 72 }
69 }; 73 };
70 74
75 let mut hl = highlights::Highlights::new(range_to_highlight);
76 traverse(&mut hl, &sema, &root, range_to_highlight, syntactic_name_ref_highlighting);
77 hl.to_vec()
78}
79
80fn traverse(
81 hl: &mut Highlights,
82 sema: &Semantics<RootDatabase>,
83 root: &SyntaxNode,
84 range_to_highlight: TextRange,
85 syntactic_name_ref_highlighting: bool,
86) {
71 let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default(); 87 let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default();
72 // We use a stack for the DFS traversal below.
73 // When we leave a node, the we use it to flatten the highlighted ranges.
74 let mut stack = HighlightedRangeStack::new();
75 88
76 let mut current_macro_call: Option<ast::MacroCall> = None; 89 let mut current_macro_call: Option<ast::MacroCall> = None;
77 let mut current_macro_rules: Option<ast::MacroRules> = None; 90 let mut current_macro_rules: Option<ast::MacroRules> = None;
78 let mut format_string_highlighter = FormatStringHighlighter::default();
79 let mut macro_rules_highlighter = MacroRulesHighlighter::default(); 91 let mut macro_rules_highlighter = MacroRulesHighlighter::default();
80 let mut inside_attribute = false; 92 let mut inside_attribute = false;
81 93
82 // Walk all nodes, keeping track of whether we are inside a macro or not. 94 // Walk all nodes, keeping track of whether we are inside a macro or not.
83 // If in macro, expand it first and highlight the expanded code. 95 // If in macro, expand it first and highlight the expanded code.
84 for event in root.preorder_with_tokens() { 96 for event in root.preorder_with_tokens() {
85 match &event {
86 WalkEvent::Enter(_) => stack.push(),
87 WalkEvent::Leave(_) => stack.pop(),
88 };
89
90 let event_range = match &event { 97 let event_range = match &event {
91 WalkEvent::Enter(it) => it.text_range(), 98 WalkEvent::Enter(it) | WalkEvent::Leave(it) => it.text_range(),
92 WalkEvent::Leave(it) => it.text_range(),
93 }; 99 };
94 100
95 // Element outside of the viewport, no need to highlight 101 // Element outside of the viewport, no need to highlight
@@ -101,9 +107,9 @@ pub(crate) fn highlight(
101 match event.clone().map(|it| it.into_node().and_then(ast::MacroCall::cast)) { 107 match event.clone().map(|it| it.into_node().and_then(ast::MacroCall::cast)) {
102 WalkEvent::Enter(Some(mc)) => { 108 WalkEvent::Enter(Some(mc)) => {
103 if let Some(range) = macro_call_range(&mc) { 109 if let Some(range) = macro_call_range(&mc) {
104 stack.add(HighlightedRange { 110 hl.add(HlRange {
105 range, 111 range,
106 highlight: HighlightTag::Symbol(SymbolKind::Macro).into(), 112 highlight: HlTag::Symbol(SymbolKind::Macro).into(),
107 binding_hash: None, 113 binding_hash: None,
108 }); 114 });
109 } 115 }
@@ -113,7 +119,6 @@ pub(crate) fn highlight(
113 WalkEvent::Leave(Some(mc)) => { 119 WalkEvent::Leave(Some(mc)) => {
114 assert_eq!(current_macro_call, Some(mc)); 120 assert_eq!(current_macro_call, Some(mc));
115 current_macro_call = None; 121 current_macro_call = None;
116 format_string_highlighter = FormatStringHighlighter::default();
117 } 122 }
118 _ => (), 123 _ => (),
119 } 124 }
@@ -131,33 +136,24 @@ pub(crate) fn highlight(
131 } 136 }
132 _ => (), 137 _ => (),
133 } 138 }
134
135 match &event { 139 match &event {
136 // Check for Rust code in documentation
137 WalkEvent::Leave(NodeOrToken::Node(node)) => {
138 if ast::Attr::can_cast(node.kind()) {
139 inside_attribute = false
140 }
141 if let Some((doctest, range_mapping, new_comments)) =
142 injection::extract_doc_comments(node)
143 {
144 injection::highlight_doc_comment(
145 doctest,
146 range_mapping,
147 new_comments,
148 &mut stack,
149 );
150 }
151 }
152 WalkEvent::Enter(NodeOrToken::Node(node)) if ast::Attr::can_cast(node.kind()) => { 140 WalkEvent::Enter(NodeOrToken::Node(node)) if ast::Attr::can_cast(node.kind()) => {
153 inside_attribute = true 141 inside_attribute = true
154 } 142 }
143 WalkEvent::Leave(NodeOrToken::Node(node)) if ast::Attr::can_cast(node.kind()) => {
144 inside_attribute = false
145 }
155 _ => (), 146 _ => (),
156 } 147 }
157 148
158 let element = match event { 149 let element = match event {
159 WalkEvent::Enter(it) => it, 150 WalkEvent::Enter(it) => it,
160 WalkEvent::Leave(_) => continue, 151 WalkEvent::Leave(it) => {
152 if let Some(node) = it.as_node() {
153 inject::doc_comment(hl, node);
154 }
155 continue;
156 }
161 }; 157 };
162 158
163 let range = element.text_range(); 159 let range = element.text_range();
@@ -177,8 +173,6 @@ pub(crate) fn highlight(
177 let token = sema.descend_into_macros(token.clone()); 173 let token = sema.descend_into_macros(token.clone());
178 let parent = token.parent(); 174 let parent = token.parent();
179 175
180 format_string_highlighter.check_for_format_string(&parent);
181
182 // We only care Name and Name_ref 176 // We only care Name and Name_ref
183 match (token.kind(), parent.kind()) { 177 match (token.kind(), parent.kind()) {
184 (IDENT, NAME) | (IDENT, NAME_REF) => parent.into(), 178 (IDENT, NAME) | (IDENT, NAME_REF) => parent.into(),
@@ -191,213 +185,45 @@ pub(crate) fn highlight(
191 if let Some(token) = element.as_token().cloned().and_then(ast::String::cast) { 185 if let Some(token) = element.as_token().cloned().and_then(ast::String::cast) {
192 if token.is_raw() { 186 if token.is_raw() {
193 let expanded = element_to_highlight.as_token().unwrap().clone(); 187 let expanded = element_to_highlight.as_token().unwrap().clone();
194 if injection::highlight_injection(&mut stack, &sema, token, expanded).is_some() { 188 if inject::ra_fixture(hl, &sema, token, expanded).is_some() {
195 continue; 189 continue;
196 } 190 }
197 } 191 }
198 } 192 }
199 193
200 if let Some((mut highlight, binding_hash)) = highlight_element( 194 if let Some(_) = macro_rules_highlighter.highlight(element_to_highlight.clone()) {
195 continue;
196 }
197
198 if let Some((mut highlight, binding_hash)) = highlight::element(
201 &sema, 199 &sema,
202 &mut bindings_shadow_count, 200 &mut bindings_shadow_count,
203 syntactic_name_ref_highlighting, 201 syntactic_name_ref_highlighting,
204 element_to_highlight.clone(), 202 element_to_highlight.clone(),
205 ) { 203 ) {
206 if inside_attribute { 204 if inside_attribute {
207 highlight = highlight | HighlightModifier::Attribute; 205 highlight = highlight | HlMod::Attribute;
208 }
209
210 if macro_rules_highlighter.highlight(element_to_highlight.clone()).is_none() {
211 stack.add(HighlightedRange { range, highlight, binding_hash });
212 } 206 }
213 207
214 if let Some(string) = 208 hl.add(HlRange { range, highlight, binding_hash });
215 element_to_highlight.as_token().cloned().and_then(ast::String::cast)
216 {
217 format_string_highlighter.highlight_format_string(&mut stack, &string, range);
218 // Highlight escape sequences
219 if let Some(char_ranges) = string.char_ranges() {
220 stack.push();
221 for (piece_range, _) in char_ranges.iter().filter(|(_, char)| char.is_ok()) {
222 if string.text()[piece_range.start().into()..].starts_with('\\') {
223 stack.add(HighlightedRange {
224 range: piece_range + range.start(),
225 highlight: HighlightTag::EscapeSequence.into(),
226 binding_hash: None,
227 });
228 }
229 }
230 stack.pop_and_inject(None);
231 }
232 }
233 } 209 }
234 }
235
236 stack.flattened()
237}
238
239#[derive(Debug)]
240struct HighlightedRangeStack {
241 stack: Vec<Vec<HighlightedRange>>,
242}
243
244/// We use a stack to implement the flattening logic for the highlighted
245/// syntax ranges.
246impl HighlightedRangeStack {
247 fn new() -> Self {
248 Self { stack: vec![Vec::new()] }
249 }
250
251 fn push(&mut self) {
252 self.stack.push(Vec::new());
253 }
254
255 /// Flattens the highlighted ranges.
256 ///
257 /// For example `#[cfg(feature = "foo")]` contains the nested ranges:
258 /// 1) parent-range: Attribute [0, 23)
259 /// 2) child-range: String [16, 21)
260 ///
261 /// The following code implements the flattening, for our example this results to:
262 /// `[Attribute [0, 16), String [16, 21), Attribute [21, 23)]`
263 fn pop(&mut self) {
264 let children = self.stack.pop().unwrap();
265 let prev = self.stack.last_mut().unwrap();
266 let needs_flattening = !children.is_empty()
267 && !prev.is_empty()
268 && prev.last().unwrap().range.contains_range(children.first().unwrap().range);
269 if !needs_flattening {
270 prev.extend(children);
271 } else {
272 let mut parent = prev.pop().unwrap();
273 for ele in children {
274 assert!(parent.range.contains_range(ele.range));
275
276 let cloned = Self::intersect(&mut parent, &ele);
277 if !parent.range.is_empty() {
278 prev.push(parent);
279 }
280 prev.push(ele);
281 parent = cloned;
282 }
283 if !parent.range.is_empty() {
284 prev.push(parent);
285 }
286 }
287 }
288
289 /// Intersects the `HighlightedRange` `parent` with `child`.
290 /// `parent` is mutated in place, becoming the range before `child`.
291 /// Returns the range (of the same type as `parent`) *after* `child`.
292 fn intersect(parent: &mut HighlightedRange, child: &HighlightedRange) -> HighlightedRange {
293 assert!(parent.range.contains_range(child.range));
294
295 let mut cloned = parent.clone();
296 parent.range = TextRange::new(parent.range.start(), child.range.start());
297 cloned.range = TextRange::new(child.range.end(), cloned.range.end());
298 210
299 cloned 211 if let Some(string) = element_to_highlight.as_token().cloned().and_then(ast::String::cast) {
300 } 212 highlight_format_string(hl, &string, range);
301 213 // Highlight escape sequences
302 /// Remove the `HighlightRange` of `parent` that's currently covered by `child`. 214 if let Some(char_ranges) = string.char_ranges() {
303 fn intersect_partial(parent: &mut HighlightedRange, child: &HighlightedRange) { 215 for (piece_range, _) in char_ranges.iter().filter(|(_, char)| char.is_ok()) {
304 assert!( 216 if string.text()[piece_range.start().into()..].starts_with('\\') {
305 parent.range.start() <= child.range.start() 217 hl.add(HlRange {
306 && parent.range.end() >= child.range.start() 218 range: piece_range + range.start(),
307 && child.range.end() > parent.range.end() 219 highlight: HlTag::EscapeSequence.into(),
308 ); 220 binding_hash: None,
309 221 });
310 parent.range = TextRange::new(parent.range.start(), child.range.start());
311 }
312
313 /// Similar to `pop`, but can modify arbitrary prior ranges (where `pop`)
314 /// can only modify the last range currently on the stack.
315 /// Can be used to do injections that span multiple ranges, like the
316 /// doctest injection below.
317 /// If `overwrite_parent` is non-optional, the highlighting of the parent range
318 /// is overwritten with the argument.
319 ///
320 /// Note that `pop` can be simulated by `pop_and_inject(false)` but the
321 /// latter is computationally more expensive.
322 fn pop_and_inject(&mut self, overwrite_parent: Option<Highlight>) {
323 let mut children = self.stack.pop().unwrap();
324 let prev = self.stack.last_mut().unwrap();
325 children.sort_by_key(|range| range.range.start());
326 prev.sort_by_key(|range| range.range.start());
327
328 for child in children {
329 if let Some(idx) =
330 prev.iter().position(|parent| parent.range.contains_range(child.range))
331 {
332 if let Some(tag) = overwrite_parent {
333 prev[idx].highlight = tag;
334 }
335
336 let cloned = Self::intersect(&mut prev[idx], &child);
337 let insert_idx = if prev[idx].range.is_empty() {
338 prev.remove(idx);
339 idx
340 } else {
341 idx + 1
342 };
343 prev.insert(insert_idx, child);
344 if !cloned.range.is_empty() {
345 prev.insert(insert_idx + 1, cloned);
346 }
347 } else {
348 let maybe_idx =
349 prev.iter().position(|parent| parent.range.contains(child.range.start()));
350 match (overwrite_parent, maybe_idx) {
351 (Some(_), Some(idx)) => {
352 Self::intersect_partial(&mut prev[idx], &child);
353 let insert_idx = if prev[idx].range.is_empty() {
354 prev.remove(idx);
355 idx
356 } else {
357 idx + 1
358 };
359 prev.insert(insert_idx, child);
360 }
361 (_, None) => {
362 let idx = prev
363 .binary_search_by_key(&child.range.start(), |range| range.range.start())
364 .unwrap_or_else(|x| x);
365 prev.insert(idx, child);
366 }
367 _ => {
368 unreachable!("child range should be completely contained in parent range");
369 } 222 }
370 } 223 }
371 } 224 }
372 } 225 }
373 } 226 }
374
375 fn add(&mut self, range: HighlightedRange) {
376 self.stack
377 .last_mut()
378 .expect("during DFS traversal, the stack must not be empty")
379 .push(range)
380 }
381
382 fn flattened(mut self) -> Vec<HighlightedRange> {
383 assert_eq!(
384 self.stack.len(),
385 1,
386 "after DFS traversal, the stack should only contain a single element"
387 );
388 let mut res = self.stack.pop().unwrap();
389 res.sort_by_key(|range| range.range.start());
390 // Check that ranges are sorted and disjoint
391 for (left, right) in res.iter().zip(res.iter().skip(1)) {
392 assert!(
393 left.range.end() <= right.range.start(),
394 "left: {:#?}, right: {:#?}",
395 left,
396 right
397 );
398 }
399 res
400 }
401} 227}
402 228
403fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> { 229fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> {
@@ -415,524 +241,3 @@ fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> {
415 241
416 Some(TextRange::new(range_start, range_end)) 242 Some(TextRange::new(range_start, range_end))
417} 243}
418
419/// Returns true if the parent nodes of `node` all match the `SyntaxKind`s in `kinds` exactly.
420fn parents_match(mut node: NodeOrToken<SyntaxNode, SyntaxToken>, mut kinds: &[SyntaxKind]) -> bool {
421 while let (Some(parent), [kind, rest @ ..]) = (&node.parent(), kinds) {
422 if parent.kind() != *kind {
423 return false;
424 }
425
426 // FIXME: Would be nice to get parent out of the match, but binding by-move and by-value
427 // in the same pattern is unstable: rust-lang/rust#68354.
428 node = node.parent().unwrap().into();
429 kinds = rest;
430 }
431
432 // Only true if we matched all expected kinds
433 kinds.len() == 0
434}
435
436fn is_consumed_lvalue(
437 node: NodeOrToken<SyntaxNode, SyntaxToken>,
438 local: &Local,
439 db: &RootDatabase,
440) -> bool {
441 // When lvalues are passed as arguments and they're not Copy, then mark them as Consuming.
442 parents_match(node, &[PATH_SEGMENT, PATH, PATH_EXPR, ARG_LIST]) && !local.ty(db).is_copy(db)
443}
444
445fn highlight_element(
446 sema: &Semantics<RootDatabase>,
447 bindings_shadow_count: &mut FxHashMap<Name, u32>,
448 syntactic_name_ref_highlighting: bool,
449 element: SyntaxElement,
450) -> Option<(Highlight, Option<u64>)> {
451 let db = sema.db;
452 let mut binding_hash = None;
453 let highlight: Highlight = match element.kind() {
454 FN => {
455 bindings_shadow_count.clear();
456 return None;
457 }
458
459 // Highlight definitions depending on the "type" of the definition.
460 NAME => {
461 let name = element.into_node().and_then(ast::Name::cast).unwrap();
462 let name_kind = NameClass::classify(sema, &name);
463
464 if let Some(NameClass::Definition(Definition::Local(local))) = &name_kind {
465 if let Some(name) = local.name(db) {
466 let shadow_count = bindings_shadow_count.entry(name.clone()).or_default();
467 *shadow_count += 1;
468 binding_hash = Some(calc_binding_hash(&name, *shadow_count))
469 }
470 };
471
472 match name_kind {
473 Some(NameClass::ExternCrate(_)) => HighlightTag::Symbol(SymbolKind::Module).into(),
474 Some(NameClass::Definition(def)) => {
475 highlight_def(db, def) | HighlightModifier::Definition
476 }
477 Some(NameClass::ConstReference(def)) => highlight_def(db, def),
478 Some(NameClass::PatFieldShorthand { field_ref, .. }) => {
479 let mut h = HighlightTag::Symbol(SymbolKind::Field).into();
480 if let Definition::Field(field) = field_ref {
481 if let VariantDef::Union(_) = field.parent_def(db) {
482 h |= HighlightModifier::Unsafe;
483 }
484 }
485
486 h
487 }
488 None => highlight_name_by_syntax(name) | HighlightModifier::Definition,
489 }
490 }
491
492 // Highlight references like the definitions they resolve to
493 NAME_REF if element.ancestors().any(|it| it.kind() == ATTR) => {
494 // even though we track whether we are in an attribute or not we still need this special case
495 // as otherwise we would emit unresolved references for name refs inside attributes
496 Highlight::from(HighlightTag::Symbol(SymbolKind::Function))
497 }
498 NAME_REF => {
499 let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap();
500 highlight_func_by_name_ref(sema, &name_ref).unwrap_or_else(|| {
501 match NameRefClass::classify(sema, &name_ref) {
502 Some(name_kind) => match name_kind {
503 NameRefClass::ExternCrate(_) => {
504 HighlightTag::Symbol(SymbolKind::Module).into()
505 }
506 NameRefClass::Definition(def) => {
507 if let Definition::Local(local) = &def {
508 if let Some(name) = local.name(db) {
509 let shadow_count =
510 bindings_shadow_count.entry(name.clone()).or_default();
511 binding_hash = Some(calc_binding_hash(&name, *shadow_count))
512 }
513 };
514
515 let mut h = highlight_def(db, def);
516
517 if let Definition::Local(local) = &def {
518 if is_consumed_lvalue(name_ref.syntax().clone().into(), local, db) {
519 h |= HighlightModifier::Consuming;
520 }
521 }
522
523 if let Some(parent) = name_ref.syntax().parent() {
524 if matches!(parent.kind(), FIELD_EXPR | RECORD_PAT_FIELD) {
525 if let Definition::Field(field) = def {
526 if let VariantDef::Union(_) = field.parent_def(db) {
527 h |= HighlightModifier::Unsafe;
528 }
529 }
530 }
531 }
532
533 h
534 }
535 NameRefClass::FieldShorthand { .. } => {
536 HighlightTag::Symbol(SymbolKind::Field).into()
537 }
538 },
539 None if syntactic_name_ref_highlighting => {
540 highlight_name_ref_by_syntax(name_ref, sema)
541 }
542 None => HighlightTag::UnresolvedReference.into(),
543 }
544 })
545 }
546
547 // Simple token-based highlighting
548 COMMENT => {
549 let comment = element.into_token().and_then(ast::Comment::cast)?;
550 let h = HighlightTag::Comment;
551 match comment.kind().doc {
552 Some(_) => h | HighlightModifier::Documentation,
553 None => h.into(),
554 }
555 }
556 STRING | BYTE_STRING => HighlightTag::StringLiteral.into(),
557 ATTR => HighlightTag::Attribute.into(),
558 INT_NUMBER | FLOAT_NUMBER => HighlightTag::NumericLiteral.into(),
559 BYTE => HighlightTag::ByteLiteral.into(),
560 CHAR => HighlightTag::CharLiteral.into(),
561 QUESTION => Highlight::new(HighlightTag::Operator) | HighlightModifier::ControlFlow,
562 LIFETIME => {
563 let lifetime = element.into_node().and_then(ast::Lifetime::cast).unwrap();
564
565 match NameClass::classify_lifetime(sema, &lifetime) {
566 Some(NameClass::Definition(def)) => {
567 highlight_def(db, def) | HighlightModifier::Definition
568 }
569 None => match NameRefClass::classify_lifetime(sema, &lifetime) {
570 Some(NameRefClass::Definition(def)) => highlight_def(db, def),
571 _ => Highlight::new(HighlightTag::Symbol(SymbolKind::LifetimeParam)),
572 },
573 _ => {
574 Highlight::new(HighlightTag::Symbol(SymbolKind::LifetimeParam))
575 | HighlightModifier::Definition
576 }
577 }
578 }
579 p if p.is_punct() => match p {
580 T![&] => {
581 let h = HighlightTag::Operator.into();
582 let is_unsafe = element
583 .parent()
584 .and_then(ast::RefExpr::cast)
585 .map(|ref_expr| sema.is_unsafe_ref_expr(&ref_expr))
586 .unwrap_or(false);
587 if is_unsafe {
588 h | HighlightModifier::Unsafe
589 } else {
590 h
591 }
592 }
593 T![::] | T![->] | T![=>] | T![..] | T![=] | T![@] | T![.] => {
594 HighlightTag::Operator.into()
595 }
596 T![!] if element.parent().and_then(ast::MacroCall::cast).is_some() => {
597 HighlightTag::Symbol(SymbolKind::Macro).into()
598 }
599 T![!] if element.parent().and_then(ast::NeverType::cast).is_some() => {
600 HighlightTag::BuiltinType.into()
601 }
602 T![*] if element.parent().and_then(ast::PtrType::cast).is_some() => {
603 HighlightTag::Keyword.into()
604 }
605 T![*] if element.parent().and_then(ast::PrefixExpr::cast).is_some() => {
606 let prefix_expr = element.parent().and_then(ast::PrefixExpr::cast)?;
607
608 let expr = prefix_expr.expr()?;
609 let ty = sema.type_of_expr(&expr)?;
610 if ty.is_raw_ptr() {
611 HighlightTag::Operator | HighlightModifier::Unsafe
612 } else if let Some(ast::PrefixOp::Deref) = prefix_expr.op_kind() {
613 HighlightTag::Operator.into()
614 } else {
615 HighlightTag::Punctuation.into()
616 }
617 }
618 T![-] if element.parent().and_then(ast::PrefixExpr::cast).is_some() => {
619 let prefix_expr = element.parent().and_then(ast::PrefixExpr::cast)?;
620
621 let expr = prefix_expr.expr()?;
622 match expr {
623 ast::Expr::Literal(_) => HighlightTag::NumericLiteral,
624 _ => HighlightTag::Operator,
625 }
626 .into()
627 }
628 _ if element.parent().and_then(ast::PrefixExpr::cast).is_some() => {
629 HighlightTag::Operator.into()
630 }
631 _ if element.parent().and_then(ast::BinExpr::cast).is_some() => {
632 HighlightTag::Operator.into()
633 }
634 _ if element.parent().and_then(ast::RangeExpr::cast).is_some() => {
635 HighlightTag::Operator.into()
636 }
637 _ if element.parent().and_then(ast::RangePat::cast).is_some() => {
638 HighlightTag::Operator.into()
639 }
640 _ if element.parent().and_then(ast::RestPat::cast).is_some() => {
641 HighlightTag::Operator.into()
642 }
643 _ if element.parent().and_then(ast::Attr::cast).is_some() => {
644 HighlightTag::Attribute.into()
645 }
646 _ => HighlightTag::Punctuation.into(),
647 },
648
649 k if k.is_keyword() => {
650 let h = Highlight::new(HighlightTag::Keyword);
651 match k {
652 T![break]
653 | T![continue]
654 | T![else]
655 | T![if]
656 | T![loop]
657 | T![match]
658 | T![return]
659 | T![while]
660 | T![in] => h | HighlightModifier::ControlFlow,
661 T![for] if !is_child_of_impl(&element) => h | HighlightModifier::ControlFlow,
662 T![unsafe] => h | HighlightModifier::Unsafe,
663 T![true] | T![false] => HighlightTag::BoolLiteral.into(),
664 T![self] => {
665 let self_param_is_mut = element
666 .parent()
667 .and_then(ast::SelfParam::cast)
668 .and_then(|p| p.mut_token())
669 .is_some();
670 let self_path = &element
671 .parent()
672 .as_ref()
673 .and_then(SyntaxNode::parent)
674 .and_then(ast::Path::cast)
675 .and_then(|p| sema.resolve_path(&p));
676 let mut h = HighlightTag::Symbol(SymbolKind::SelfParam).into();
677 if self_param_is_mut
678 || matches!(self_path,
679 Some(hir::PathResolution::Local(local))
680 if local.is_self(db)
681 && (local.is_mut(db) || local.ty(db).is_mutable_reference())
682 )
683 {
684 h |= HighlightModifier::Mutable
685 }
686
687 if let Some(hir::PathResolution::Local(local)) = self_path {
688 if is_consumed_lvalue(element, &local, db) {
689 h |= HighlightModifier::Consuming;
690 }
691 }
692
693 h
694 }
695 T![ref] => element
696 .parent()
697 .and_then(ast::IdentPat::cast)
698 .and_then(|ident_pat| {
699 if sema.is_unsafe_ident_pat(&ident_pat) {
700 Some(HighlightModifier::Unsafe)
701 } else {
702 None
703 }
704 })
705 .map(|modifier| h | modifier)
706 .unwrap_or(h),
707 _ => h,
708 }
709 }
710
711 _ => return None,
712 };
713
714 return Some((highlight, binding_hash));
715
716 fn calc_binding_hash(name: &Name, shadow_count: u32) -> u64 {
717 fn hash<T: std::hash::Hash + std::fmt::Debug>(x: T) -> u64 {
718 use std::{collections::hash_map::DefaultHasher, hash::Hasher};
719
720 let mut hasher = DefaultHasher::new();
721 x.hash(&mut hasher);
722 hasher.finish()
723 }
724
725 hash((name, shadow_count))
726 }
727}
728
729fn is_child_of_impl(element: &SyntaxElement) -> bool {
730 match element.parent() {
731 Some(e) => e.kind() == IMPL,
732 _ => false,
733 }
734}
735
736fn highlight_func_by_name_ref(
737 sema: &Semantics<RootDatabase>,
738 name_ref: &ast::NameRef,
739) -> Option<Highlight> {
740 let method_call = name_ref.syntax().parent().and_then(ast::MethodCallExpr::cast)?;
741 highlight_method_call(sema, &method_call)
742}
743
744fn highlight_method_call(
745 sema: &Semantics<RootDatabase>,
746 method_call: &ast::MethodCallExpr,
747) -> Option<Highlight> {
748 let func = sema.resolve_method_call(&method_call)?;
749 let mut h = HighlightTag::Symbol(SymbolKind::Function).into();
750 h |= HighlightModifier::Associated;
751 if func.is_unsafe(sema.db) || sema.is_unsafe_method_call(&method_call) {
752 h |= HighlightModifier::Unsafe;
753 }
754 if let Some(self_param) = func.self_param(sema.db) {
755 match self_param.access(sema.db) {
756 hir::Access::Shared => (),
757 hir::Access::Exclusive => h |= HighlightModifier::Mutable,
758 hir::Access::Owned => {
759 if let Some(receiver_ty) =
760 method_call.receiver().and_then(|it| sema.type_of_expr(&it))
761 {
762 if !receiver_ty.is_copy(sema.db) {
763 h |= HighlightModifier::Consuming
764 }
765 }
766 }
767 }
768 }
769 Some(h)
770}
771
772fn highlight_def(db: &RootDatabase, def: Definition) -> Highlight {
773 match def {
774 Definition::Macro(_) => HighlightTag::Symbol(SymbolKind::Macro),
775 Definition::Field(_) => HighlightTag::Symbol(SymbolKind::Field),
776 Definition::ModuleDef(def) => match def {
777 hir::ModuleDef::Module(_) => HighlightTag::Symbol(SymbolKind::Module),
778 hir::ModuleDef::Function(func) => {
779 let mut h = Highlight::new(HighlightTag::Symbol(SymbolKind::Function));
780 if func.as_assoc_item(db).is_some() {
781 h |= HighlightModifier::Associated;
782 if func.self_param(db).is_none() {
783 h |= HighlightModifier::Static
784 }
785 }
786 if func.is_unsafe(db) {
787 h |= HighlightModifier::Unsafe;
788 }
789 return h;
790 }
791 hir::ModuleDef::Adt(hir::Adt::Struct(_)) => HighlightTag::Symbol(SymbolKind::Struct),
792 hir::ModuleDef::Adt(hir::Adt::Enum(_)) => HighlightTag::Symbol(SymbolKind::Enum),
793 hir::ModuleDef::Adt(hir::Adt::Union(_)) => HighlightTag::Symbol(SymbolKind::Union),
794 hir::ModuleDef::Variant(_) => HighlightTag::Symbol(SymbolKind::Variant),
795 hir::ModuleDef::Const(konst) => {
796 let mut h = Highlight::new(HighlightTag::Symbol(SymbolKind::Const));
797 if konst.as_assoc_item(db).is_some() {
798 h |= HighlightModifier::Associated
799 }
800 return h;
801 }
802 hir::ModuleDef::Trait(_) => HighlightTag::Symbol(SymbolKind::Trait),
803 hir::ModuleDef::TypeAlias(type_) => {
804 let mut h = Highlight::new(HighlightTag::Symbol(SymbolKind::TypeAlias));
805 if type_.as_assoc_item(db).is_some() {
806 h |= HighlightModifier::Associated
807 }
808 return h;
809 }
810 hir::ModuleDef::BuiltinType(_) => HighlightTag::BuiltinType,
811 hir::ModuleDef::Static(s) => {
812 let mut h = Highlight::new(HighlightTag::Symbol(SymbolKind::Static));
813 if s.is_mut(db) {
814 h |= HighlightModifier::Mutable;
815 h |= HighlightModifier::Unsafe;
816 }
817 return h;
818 }
819 },
820 Definition::SelfType(_) => HighlightTag::Symbol(SymbolKind::Impl),
821 Definition::TypeParam(_) => HighlightTag::Symbol(SymbolKind::TypeParam),
822 Definition::ConstParam(_) => HighlightTag::Symbol(SymbolKind::ConstParam),
823 Definition::Local(local) => {
824 let tag = if local.is_param(db) {
825 HighlightTag::Symbol(SymbolKind::ValueParam)
826 } else {
827 HighlightTag::Symbol(SymbolKind::Local)
828 };
829 let mut h = Highlight::new(tag);
830 if local.is_mut(db) || local.ty(db).is_mutable_reference() {
831 h |= HighlightModifier::Mutable;
832 }
833 if local.ty(db).as_callable(db).is_some() || local.ty(db).impls_fnonce(db) {
834 h |= HighlightModifier::Callable;
835 }
836 return h;
837 }
838 Definition::LifetimeParam(_) => HighlightTag::Symbol(SymbolKind::LifetimeParam),
839 Definition::Label(_) => HighlightTag::Symbol(SymbolKind::Label),
840 }
841 .into()
842}
843
844fn highlight_name_by_syntax(name: ast::Name) -> Highlight {
845 let default = HighlightTag::UnresolvedReference;
846
847 let parent = match name.syntax().parent() {
848 Some(it) => it,
849 _ => return default.into(),
850 };
851
852 let tag = match parent.kind() {
853 STRUCT => HighlightTag::Symbol(SymbolKind::Struct),
854 ENUM => HighlightTag::Symbol(SymbolKind::Enum),
855 VARIANT => HighlightTag::Symbol(SymbolKind::Variant),
856 UNION => HighlightTag::Symbol(SymbolKind::Union),
857 TRAIT => HighlightTag::Symbol(SymbolKind::Trait),
858 TYPE_ALIAS => HighlightTag::Symbol(SymbolKind::TypeAlias),
859 TYPE_PARAM => HighlightTag::Symbol(SymbolKind::TypeParam),
860 RECORD_FIELD => HighlightTag::Symbol(SymbolKind::Field),
861 MODULE => HighlightTag::Symbol(SymbolKind::Module),
862 FN => HighlightTag::Symbol(SymbolKind::Function),
863 CONST => HighlightTag::Symbol(SymbolKind::Const),
864 STATIC => HighlightTag::Symbol(SymbolKind::Static),
865 IDENT_PAT => HighlightTag::Symbol(SymbolKind::Local),
866 _ => default,
867 };
868
869 tag.into()
870}
871
872fn highlight_name_ref_by_syntax(name: ast::NameRef, sema: &Semantics<RootDatabase>) -> Highlight {
873 let default = HighlightTag::UnresolvedReference;
874
875 let parent = match name.syntax().parent() {
876 Some(it) => it,
877 _ => return default.into(),
878 };
879
880 match parent.kind() {
881 METHOD_CALL_EXPR => {
882 return ast::MethodCallExpr::cast(parent)
883 .and_then(|method_call| highlight_method_call(sema, &method_call))
884 .unwrap_or_else(|| HighlightTag::Symbol(SymbolKind::Function).into());
885 }
886 FIELD_EXPR => {
887 let h = HighlightTag::Symbol(SymbolKind::Field);
888 let is_union = ast::FieldExpr::cast(parent)
889 .and_then(|field_expr| {
890 let field = sema.resolve_field(&field_expr)?;
891 Some(if let VariantDef::Union(_) = field.parent_def(sema.db) {
892 true
893 } else {
894 false
895 })
896 })
897 .unwrap_or(false);
898 if is_union {
899 h | HighlightModifier::Unsafe
900 } else {
901 h.into()
902 }
903 }
904 PATH_SEGMENT => {
905 let path = match parent.parent().and_then(ast::Path::cast) {
906 Some(it) => it,
907 _ => return default.into(),
908 };
909 let expr = match path.syntax().parent().and_then(ast::PathExpr::cast) {
910 Some(it) => it,
911 _ => {
912 // within path, decide whether it is module or adt by checking for uppercase name
913 return if name.text().chars().next().unwrap_or_default().is_uppercase() {
914 HighlightTag::Symbol(SymbolKind::Struct)
915 } else {
916 HighlightTag::Symbol(SymbolKind::Module)
917 }
918 .into();
919 }
920 };
921 let parent = match expr.syntax().parent() {
922 Some(it) => it,
923 None => return default.into(),
924 };
925
926 match parent.kind() {
927 CALL_EXPR => HighlightTag::Symbol(SymbolKind::Function).into(),
928 _ => if name.text().chars().next().unwrap_or_default().is_uppercase() {
929 HighlightTag::Symbol(SymbolKind::Struct)
930 } else {
931 HighlightTag::Symbol(SymbolKind::Const)
932 }
933 .into(),
934 }
935 }
936 _ => default.into(),
937 }
938}
diff --git a/crates/ide/src/syntax_highlighting/format.rs b/crates/ide/src/syntax_highlighting/format.rs
index 26416022b..a74ca844b 100644
--- a/crates/ide/src/syntax_highlighting/format.rs
+++ b/crates/ide/src/syntax_highlighting/format.rs
@@ -1,65 +1,51 @@
1//! Syntax highlighting for format macro strings. 1//! Syntax highlighting for format macro strings.
2use syntax::{ 2use syntax::{
3 ast::{self, FormatSpecifier, HasFormatSpecifier}, 3 ast::{self, FormatSpecifier, HasFormatSpecifier},
4 AstNode, AstToken, SyntaxElement, SyntaxKind, SyntaxNode, TextRange, 4 AstNode, AstToken, TextRange,
5}; 5};
6 6
7use crate::{ 7use crate::{syntax_highlighting::highlights::Highlights, HlRange, HlTag, SymbolKind};
8 syntax_highlighting::HighlightedRangeStack, HighlightTag, HighlightedRange, SymbolKind,
9};
10
11#[derive(Default)]
12pub(super) struct FormatStringHighlighter {
13 format_string: Option<SyntaxElement>,
14}
15 8
16impl FormatStringHighlighter { 9pub(super) fn highlight_format_string(
17 pub(super) fn check_for_format_string(&mut self, parent: &SyntaxNode) { 10 stack: &mut Highlights,
18 // Check if macro takes a format string and remember it for highlighting later. 11 string: &ast::String,
19 // The macros that accept a format string expand to a compiler builtin macros 12 range: TextRange,
20 // `format_args` and `format_args_nl`. 13) {
21 if let Some(name) = parent 14 if is_format_string(string).is_none() {
22 .parent() 15 return;
23 .and_then(ast::MacroCall::cast)
24 .and_then(|mc| mc.path())
25 .and_then(|p| p.segment())
26 .and_then(|s| s.name_ref())
27 {
28 match name.text().as_str() {
29 "format_args" | "format_args_nl" => {
30 self.format_string = parent
31 .children_with_tokens()
32 .filter(|t| t.kind() != SyntaxKind::WHITESPACE)
33 .nth(1)
34 .filter(|e| ast::String::can_cast(e.kind()))
35 }
36 _ => {}
37 }
38 }
39 } 16 }
40 pub(super) fn highlight_format_string( 17
41 &self, 18 string.lex_format_specifier(|piece_range, kind| {
42 range_stack: &mut HighlightedRangeStack, 19 if let Some(highlight) = highlight_format_specifier(kind) {
43 string: &impl HasFormatSpecifier, 20 stack.add(HlRange {
44 range: TextRange, 21 range: piece_range + range.start(),
45 ) { 22 highlight: highlight.into(),
46 if self.format_string.as_ref() == Some(&SyntaxElement::from(string.syntax().clone())) { 23 binding_hash: None,
47 range_stack.push();
48 string.lex_format_specifier(|piece_range, kind| {
49 if let Some(highlight) = highlight_format_specifier(kind) {
50 range_stack.add(HighlightedRange {
51 range: piece_range + range.start(),
52 highlight: highlight.into(),
53 binding_hash: None,
54 });
55 }
56 }); 24 });
57 range_stack.pop();
58 } 25 }
26 });
27}
28
29fn is_format_string(string: &ast::String) -> Option<()> {
30 let parent = string.syntax().parent();
31
32 let name = parent.parent().and_then(ast::MacroCall::cast)?.path()?.segment()?.name_ref()?;
33 if !matches!(name.text().as_str(), "format_args" | "format_args_nl") {
34 return None;
35 }
36
37 let first_literal = parent
38 .children_with_tokens()
39 .filter_map(|it| it.as_token().cloned().and_then(ast::String::cast))
40 .next()?;
41 if &first_literal != string {
42 return None;
59 } 43 }
44
45 Some(())
60} 46}
61 47
62fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HighlightTag> { 48fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HlTag> {
63 Some(match kind { 49 Some(match kind {
64 FormatSpecifier::Open 50 FormatSpecifier::Open
65 | FormatSpecifier::Close 51 | FormatSpecifier::Close
@@ -71,8 +57,10 @@ fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HighlightTag> {
71 | FormatSpecifier::DollarSign 57 | FormatSpecifier::DollarSign
72 | FormatSpecifier::Dot 58 | FormatSpecifier::Dot
73 | FormatSpecifier::Asterisk 59 | FormatSpecifier::Asterisk
74 | FormatSpecifier::QuestionMark => HighlightTag::FormatSpecifier, 60 | FormatSpecifier::QuestionMark => HlTag::FormatSpecifier,
75 FormatSpecifier::Integer | FormatSpecifier::Zero => HighlightTag::NumericLiteral, 61
76 FormatSpecifier::Identifier => HighlightTag::Symbol(SymbolKind::Local), 62 FormatSpecifier::Integer | FormatSpecifier::Zero => HlTag::NumericLiteral,
63
64 FormatSpecifier::Identifier => HlTag::Symbol(SymbolKind::Local),
77 }) 65 })
78} 66}
diff --git a/crates/ide/src/syntax_highlighting/highlight.rs b/crates/ide/src/syntax_highlighting/highlight.rs
new file mode 100644
index 000000000..34bae49a8
--- /dev/null
+++ b/crates/ide/src/syntax_highlighting/highlight.rs
@@ -0,0 +1,530 @@
1//! Computes color for a single element.
2
3use hir::{AsAssocItem, Semantics, VariantDef};
4use ide_db::{
5 defs::{Definition, NameClass, NameRefClass},
6 RootDatabase,
7};
8use rustc_hash::FxHashMap;
9use syntax::{
10 ast, AstNode, AstToken, NodeOrToken, SyntaxElement,
11 SyntaxKind::{self, *},
12 SyntaxNode, SyntaxToken, T,
13};
14
15use crate::{syntax_highlighting::tags::HlPunct, Highlight, HlMod, HlTag, SymbolKind};
16
17pub(super) fn element(
18 sema: &Semantics<RootDatabase>,
19 bindings_shadow_count: &mut FxHashMap<hir::Name, u32>,
20 syntactic_name_ref_highlighting: bool,
21 element: SyntaxElement,
22) -> Option<(Highlight, Option<u64>)> {
23 let db = sema.db;
24 let mut binding_hash = None;
25 let highlight: Highlight = match element.kind() {
26 FN => {
27 bindings_shadow_count.clear();
28 return None;
29 }
30
31 // Highlight definitions depending on the "type" of the definition.
32 NAME => {
33 let name = element.into_node().and_then(ast::Name::cast).unwrap();
34 let name_kind = NameClass::classify(sema, &name);
35
36 if let Some(NameClass::Definition(Definition::Local(local))) = &name_kind {
37 if let Some(name) = local.name(db) {
38 let shadow_count = bindings_shadow_count.entry(name.clone()).or_default();
39 *shadow_count += 1;
40 binding_hash = Some(calc_binding_hash(&name, *shadow_count))
41 }
42 };
43
44 match name_kind {
45 Some(NameClass::ExternCrate(_)) => HlTag::Symbol(SymbolKind::Module).into(),
46 Some(NameClass::Definition(def)) => highlight_def(db, def) | HlMod::Definition,
47 Some(NameClass::ConstReference(def)) => highlight_def(db, def),
48 Some(NameClass::PatFieldShorthand { field_ref, .. }) => {
49 let mut h = HlTag::Symbol(SymbolKind::Field).into();
50 if let Definition::Field(field) = field_ref {
51 if let VariantDef::Union(_) = field.parent_def(db) {
52 h |= HlMod::Unsafe;
53 }
54 }
55
56 h
57 }
58 None => highlight_name_by_syntax(name) | HlMod::Definition,
59 }
60 }
61
62 // Highlight references like the definitions they resolve to
63 NAME_REF if element.ancestors().any(|it| it.kind() == ATTR) => {
64 // even though we track whether we are in an attribute or not we still need this special case
65 // as otherwise we would emit unresolved references for name refs inside attributes
66 Highlight::from(HlTag::Symbol(SymbolKind::Function))
67 }
68 NAME_REF => {
69 let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap();
70 highlight_func_by_name_ref(sema, &name_ref).unwrap_or_else(|| {
71 match NameRefClass::classify(sema, &name_ref) {
72 Some(name_kind) => match name_kind {
73 NameRefClass::ExternCrate(_) => HlTag::Symbol(SymbolKind::Module).into(),
74 NameRefClass::Definition(def) => {
75 if let Definition::Local(local) = &def {
76 if let Some(name) = local.name(db) {
77 let shadow_count =
78 bindings_shadow_count.entry(name.clone()).or_default();
79 binding_hash = Some(calc_binding_hash(&name, *shadow_count))
80 }
81 };
82
83 let mut h = highlight_def(db, def);
84
85 if let Definition::Local(local) = &def {
86 if is_consumed_lvalue(name_ref.syntax().clone().into(), local, db) {
87 h |= HlMod::Consuming;
88 }
89 }
90
91 if let Some(parent) = name_ref.syntax().parent() {
92 if matches!(parent.kind(), FIELD_EXPR | RECORD_PAT_FIELD) {
93 if let Definition::Field(field) = def {
94 if let VariantDef::Union(_) = field.parent_def(db) {
95 h |= HlMod::Unsafe;
96 }
97 }
98 }
99 }
100
101 h
102 }
103 NameRefClass::FieldShorthand { .. } => {
104 HlTag::Symbol(SymbolKind::Field).into()
105 }
106 },
107 None if syntactic_name_ref_highlighting => {
108 highlight_name_ref_by_syntax(name_ref, sema)
109 }
110 None => HlTag::UnresolvedReference.into(),
111 }
112 })
113 }
114
115 // Simple token-based highlighting
116 COMMENT => {
117 let comment = element.into_token().and_then(ast::Comment::cast)?;
118 let h = HlTag::Comment;
119 match comment.kind().doc {
120 Some(_) => h | HlMod::Documentation,
121 None => h.into(),
122 }
123 }
124 STRING | BYTE_STRING => HlTag::StringLiteral.into(),
125 ATTR => HlTag::Attribute.into(),
126 INT_NUMBER | FLOAT_NUMBER => HlTag::NumericLiteral.into(),
127 BYTE => HlTag::ByteLiteral.into(),
128 CHAR => HlTag::CharLiteral.into(),
129 QUESTION => Highlight::new(HlTag::Operator) | HlMod::ControlFlow,
130 LIFETIME => {
131 let lifetime = element.into_node().and_then(ast::Lifetime::cast).unwrap();
132
133 match NameClass::classify_lifetime(sema, &lifetime) {
134 Some(NameClass::Definition(def)) => highlight_def(db, def) | HlMod::Definition,
135 None => match NameRefClass::classify_lifetime(sema, &lifetime) {
136 Some(NameRefClass::Definition(def)) => highlight_def(db, def),
137 _ => Highlight::new(HlTag::Symbol(SymbolKind::LifetimeParam)),
138 },
139 _ => Highlight::new(HlTag::Symbol(SymbolKind::LifetimeParam)) | HlMod::Definition,
140 }
141 }
142 p if p.is_punct() => match p {
143 T![&] => {
144 let h = HlTag::Operator.into();
145 let is_unsafe = element
146 .parent()
147 .and_then(ast::RefExpr::cast)
148 .map(|ref_expr| sema.is_unsafe_ref_expr(&ref_expr))
149 .unwrap_or(false);
150 if is_unsafe {
151 h | HlMod::Unsafe
152 } else {
153 h
154 }
155 }
156 T![::] | T![->] | T![=>] | T![..] | T![=] | T![@] | T![.] => HlTag::Operator.into(),
157 T![!] if element.parent().and_then(ast::MacroCall::cast).is_some() => {
158 HlTag::Symbol(SymbolKind::Macro).into()
159 }
160 T![!] if element.parent().and_then(ast::NeverType::cast).is_some() => {
161 HlTag::BuiltinType.into()
162 }
163 T![*] if element.parent().and_then(ast::PtrType::cast).is_some() => {
164 HlTag::Keyword.into()
165 }
166 T![*] if element.parent().and_then(ast::PrefixExpr::cast).is_some() => {
167 let prefix_expr = element.parent().and_then(ast::PrefixExpr::cast)?;
168
169 let expr = prefix_expr.expr()?;
170 let ty = sema.type_of_expr(&expr)?;
171 if ty.is_raw_ptr() {
172 HlTag::Operator | HlMod::Unsafe
173 } else if let Some(ast::PrefixOp::Deref) = prefix_expr.op_kind() {
174 HlTag::Operator.into()
175 } else {
176 HlTag::Punctuation(HlPunct::Other).into()
177 }
178 }
179 T![-] if element.parent().and_then(ast::PrefixExpr::cast).is_some() => {
180 let prefix_expr = element.parent().and_then(ast::PrefixExpr::cast)?;
181
182 let expr = prefix_expr.expr()?;
183 match expr {
184 ast::Expr::Literal(_) => HlTag::NumericLiteral,
185 _ => HlTag::Operator,
186 }
187 .into()
188 }
189 _ if element.parent().and_then(ast::PrefixExpr::cast).is_some() => {
190 HlTag::Operator.into()
191 }
192 _ if element.parent().and_then(ast::BinExpr::cast).is_some() => HlTag::Operator.into(),
193 _ if element.parent().and_then(ast::RangeExpr::cast).is_some() => {
194 HlTag::Operator.into()
195 }
196 _ if element.parent().and_then(ast::RangePat::cast).is_some() => HlTag::Operator.into(),
197 _ if element.parent().and_then(ast::RestPat::cast).is_some() => HlTag::Operator.into(),
198 _ if element.parent().and_then(ast::Attr::cast).is_some() => HlTag::Attribute.into(),
199 kind => HlTag::Punctuation(match kind {
200 T!['['] | T![']'] => HlPunct::Bracket,
201 T!['{'] | T!['}'] => HlPunct::Brace,
202 T!['('] | T![')'] => HlPunct::Parenthesis,
203 T![<] | T![>] => HlPunct::Angle,
204 T![,] => HlPunct::Comma,
205 T![:] => HlPunct::Colon,
206 T![;] => HlPunct::Semi,
207 T![.] => HlPunct::Dot,
208 _ => HlPunct::Other,
209 })
210 .into(),
211 },
212
213 k if k.is_keyword() => {
214 let h = Highlight::new(HlTag::Keyword);
215 match k {
216 T![break]
217 | T![continue]
218 | T![else]
219 | T![if]
220 | T![loop]
221 | T![match]
222 | T![return]
223 | T![while]
224 | T![in] => h | HlMod::ControlFlow,
225 T![for] if !is_child_of_impl(&element) => h | HlMod::ControlFlow,
226 T![unsafe] => h | HlMod::Unsafe,
227 T![true] | T![false] => HlTag::BoolLiteral.into(),
228 T![self] => {
229 let self_param_is_mut = element
230 .parent()
231 .and_then(ast::SelfParam::cast)
232 .and_then(|p| p.mut_token())
233 .is_some();
234 let self_path = &element
235 .parent()
236 .as_ref()
237 .and_then(SyntaxNode::parent)
238 .and_then(ast::Path::cast)
239 .and_then(|p| sema.resolve_path(&p));
240 let mut h = HlTag::Symbol(SymbolKind::SelfParam).into();
241 if self_param_is_mut
242 || matches!(self_path,
243 Some(hir::PathResolution::Local(local))
244 if local.is_self(db)
245 && (local.is_mut(db) || local.ty(db).is_mutable_reference())
246 )
247 {
248 h |= HlMod::Mutable
249 }
250
251 if let Some(hir::PathResolution::Local(local)) = self_path {
252 if is_consumed_lvalue(element, &local, db) {
253 h |= HlMod::Consuming;
254 }
255 }
256
257 h
258 }
259 T![ref] => element
260 .parent()
261 .and_then(ast::IdentPat::cast)
262 .and_then(|ident_pat| {
263 if sema.is_unsafe_ident_pat(&ident_pat) {
264 Some(HlMod::Unsafe)
265 } else {
266 None
267 }
268 })
269 .map(|modifier| h | modifier)
270 .unwrap_or(h),
271 _ => h,
272 }
273 }
274
275 _ => return None,
276 };
277
278 return Some((highlight, binding_hash));
279
280 fn calc_binding_hash(name: &hir::Name, shadow_count: u32) -> u64 {
281 fn hash<T: std::hash::Hash + std::fmt::Debug>(x: T) -> u64 {
282 use std::{collections::hash_map::DefaultHasher, hash::Hasher};
283
284 let mut hasher = DefaultHasher::new();
285 x.hash(&mut hasher);
286 hasher.finish()
287 }
288
289 hash((name, shadow_count))
290 }
291}
292
293fn highlight_def(db: &RootDatabase, def: Definition) -> Highlight {
294 match def {
295 Definition::Macro(_) => HlTag::Symbol(SymbolKind::Macro),
296 Definition::Field(_) => HlTag::Symbol(SymbolKind::Field),
297 Definition::ModuleDef(def) => match def {
298 hir::ModuleDef::Module(_) => HlTag::Symbol(SymbolKind::Module),
299 hir::ModuleDef::Function(func) => {
300 let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Function));
301 if func.as_assoc_item(db).is_some() {
302 h |= HlMod::Associated;
303 if func.self_param(db).is_none() {
304 h |= HlMod::Static
305 }
306 }
307 if func.is_unsafe(db) {
308 h |= HlMod::Unsafe;
309 }
310 return h;
311 }
312 hir::ModuleDef::Adt(hir::Adt::Struct(_)) => HlTag::Symbol(SymbolKind::Struct),
313 hir::ModuleDef::Adt(hir::Adt::Enum(_)) => HlTag::Symbol(SymbolKind::Enum),
314 hir::ModuleDef::Adt(hir::Adt::Union(_)) => HlTag::Symbol(SymbolKind::Union),
315 hir::ModuleDef::Variant(_) => HlTag::Symbol(SymbolKind::Variant),
316 hir::ModuleDef::Const(konst) => {
317 let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Const));
318 if konst.as_assoc_item(db).is_some() {
319 h |= HlMod::Associated
320 }
321 return h;
322 }
323 hir::ModuleDef::Trait(_) => HlTag::Symbol(SymbolKind::Trait),
324 hir::ModuleDef::TypeAlias(type_) => {
325 let mut h = Highlight::new(HlTag::Symbol(SymbolKind::TypeAlias));
326 if type_.as_assoc_item(db).is_some() {
327 h |= HlMod::Associated
328 }
329 return h;
330 }
331 hir::ModuleDef::BuiltinType(_) => HlTag::BuiltinType,
332 hir::ModuleDef::Static(s) => {
333 let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Static));
334 if s.is_mut(db) {
335 h |= HlMod::Mutable;
336 h |= HlMod::Unsafe;
337 }
338 return h;
339 }
340 },
341 Definition::SelfType(_) => HlTag::Symbol(SymbolKind::Impl),
342 Definition::GenericParam(it) => match it {
343 hir::GenericParam::TypeParam(_) => HlTag::Symbol(SymbolKind::TypeParam),
344 hir::GenericParam::ConstParam(_) => HlTag::Symbol(SymbolKind::ConstParam),
345 hir::GenericParam::LifetimeParam(_) => HlTag::Symbol(SymbolKind::LifetimeParam),
346 },
347 Definition::Local(local) => {
348 let tag = if local.is_param(db) {
349 HlTag::Symbol(SymbolKind::ValueParam)
350 } else {
351 HlTag::Symbol(SymbolKind::Local)
352 };
353 let mut h = Highlight::new(tag);
354 if local.is_mut(db) || local.ty(db).is_mutable_reference() {
355 h |= HlMod::Mutable;
356 }
357 if local.ty(db).as_callable(db).is_some() || local.ty(db).impls_fnonce(db) {
358 h |= HlMod::Callable;
359 }
360 return h;
361 }
362 Definition::Label(_) => HlTag::Symbol(SymbolKind::Label),
363 }
364 .into()
365}
366
367fn highlight_func_by_name_ref(
368 sema: &Semantics<RootDatabase>,
369 name_ref: &ast::NameRef,
370) -> Option<Highlight> {
371 let mc = name_ref.syntax().parent().and_then(ast::MethodCallExpr::cast)?;
372 highlight_method_call(sema, &mc)
373}
374
375fn highlight_method_call(
376 sema: &Semantics<RootDatabase>,
377 method_call: &ast::MethodCallExpr,
378) -> Option<Highlight> {
379 let func = sema.resolve_method_call(&method_call)?;
380 let mut h = HlTag::Symbol(SymbolKind::Function).into();
381 h |= HlMod::Associated;
382 if func.is_unsafe(sema.db) || sema.is_unsafe_method_call(&method_call) {
383 h |= HlMod::Unsafe;
384 }
385 if let Some(self_param) = func.self_param(sema.db) {
386 match self_param.access(sema.db) {
387 hir::Access::Shared => (),
388 hir::Access::Exclusive => h |= HlMod::Mutable,
389 hir::Access::Owned => {
390 if let Some(receiver_ty) =
391 method_call.receiver().and_then(|it| sema.type_of_expr(&it))
392 {
393 if !receiver_ty.is_copy(sema.db) {
394 h |= HlMod::Consuming
395 }
396 }
397 }
398 }
399 }
400 Some(h)
401}
402
403fn highlight_name_by_syntax(name: ast::Name) -> Highlight {
404 let default = HlTag::UnresolvedReference;
405
406 let parent = match name.syntax().parent() {
407 Some(it) => it,
408 _ => return default.into(),
409 };
410
411 let tag = match parent.kind() {
412 STRUCT => HlTag::Symbol(SymbolKind::Struct),
413 ENUM => HlTag::Symbol(SymbolKind::Enum),
414 VARIANT => HlTag::Symbol(SymbolKind::Variant),
415 UNION => HlTag::Symbol(SymbolKind::Union),
416 TRAIT => HlTag::Symbol(SymbolKind::Trait),
417 TYPE_ALIAS => HlTag::Symbol(SymbolKind::TypeAlias),
418 TYPE_PARAM => HlTag::Symbol(SymbolKind::TypeParam),
419 RECORD_FIELD => HlTag::Symbol(SymbolKind::Field),
420 MODULE => HlTag::Symbol(SymbolKind::Module),
421 FN => HlTag::Symbol(SymbolKind::Function),
422 CONST => HlTag::Symbol(SymbolKind::Const),
423 STATIC => HlTag::Symbol(SymbolKind::Static),
424 IDENT_PAT => HlTag::Symbol(SymbolKind::Local),
425 _ => default,
426 };
427
428 tag.into()
429}
430
431fn highlight_name_ref_by_syntax(name: ast::NameRef, sema: &Semantics<RootDatabase>) -> Highlight {
432 let default = HlTag::UnresolvedReference;
433
434 let parent = match name.syntax().parent() {
435 Some(it) => it,
436 _ => return default.into(),
437 };
438
439 match parent.kind() {
440 METHOD_CALL_EXPR => {
441 return ast::MethodCallExpr::cast(parent)
442 .and_then(|it| highlight_method_call(sema, &it))
443 .unwrap_or_else(|| HlTag::Symbol(SymbolKind::Function).into());
444 }
445 FIELD_EXPR => {
446 let h = HlTag::Symbol(SymbolKind::Field);
447 let is_union = ast::FieldExpr::cast(parent)
448 .and_then(|field_expr| {
449 let field = sema.resolve_field(&field_expr)?;
450 Some(if let VariantDef::Union(_) = field.parent_def(sema.db) {
451 true
452 } else {
453 false
454 })
455 })
456 .unwrap_or(false);
457 if is_union {
458 h | HlMod::Unsafe
459 } else {
460 h.into()
461 }
462 }
463 PATH_SEGMENT => {
464 let path = match parent.parent().and_then(ast::Path::cast) {
465 Some(it) => it,
466 _ => return default.into(),
467 };
468 let expr = match path.syntax().parent().and_then(ast::PathExpr::cast) {
469 Some(it) => it,
470 _ => {
471 // within path, decide whether it is module or adt by checking for uppercase name
472 return if name.text().chars().next().unwrap_or_default().is_uppercase() {
473 HlTag::Symbol(SymbolKind::Struct)
474 } else {
475 HlTag::Symbol(SymbolKind::Module)
476 }
477 .into();
478 }
479 };
480 let parent = match expr.syntax().parent() {
481 Some(it) => it,
482 None => return default.into(),
483 };
484
485 match parent.kind() {
486 CALL_EXPR => HlTag::Symbol(SymbolKind::Function).into(),
487 _ => if name.text().chars().next().unwrap_or_default().is_uppercase() {
488 HlTag::Symbol(SymbolKind::Struct)
489 } else {
490 HlTag::Symbol(SymbolKind::Const)
491 }
492 .into(),
493 }
494 }
495 _ => default.into(),
496 }
497}
498
499fn is_consumed_lvalue(
500 node: NodeOrToken<SyntaxNode, SyntaxToken>,
501 local: &hir::Local,
502 db: &RootDatabase,
503) -> bool {
504 // When lvalues are passed as arguments and they're not Copy, then mark them as Consuming.
505 parents_match(node, &[PATH_SEGMENT, PATH, PATH_EXPR, ARG_LIST]) && !local.ty(db).is_copy(db)
506}
507
508/// Returns true if the parent nodes of `node` all match the `SyntaxKind`s in `kinds` exactly.
509fn parents_match(mut node: NodeOrToken<SyntaxNode, SyntaxToken>, mut kinds: &[SyntaxKind]) -> bool {
510 while let (Some(parent), [kind, rest @ ..]) = (&node.parent(), kinds) {
511 if parent.kind() != *kind {
512 return false;
513 }
514
515 // FIXME: Would be nice to get parent out of the match, but binding by-move and by-value
516 // in the same pattern is unstable: rust-lang/rust#68354.
517 node = node.parent().unwrap().into();
518 kinds = rest;
519 }
520
521 // Only true if we matched all expected kinds
522 kinds.len() == 0
523}
524
525fn is_child_of_impl(element: &SyntaxElement) -> bool {
526 match element.parent() {
527 Some(e) => e.kind() == IMPL,
528 _ => false,
529 }
530}
diff --git a/crates/ide/src/syntax_highlighting/highlights.rs b/crates/ide/src/syntax_highlighting/highlights.rs
new file mode 100644
index 000000000..882a685a5
--- /dev/null
+++ b/crates/ide/src/syntax_highlighting/highlights.rs
@@ -0,0 +1,92 @@