aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock15
-rw-r--r--crates/assists/src/handlers/remove_dbg.rs12
-rw-r--r--crates/completion/src/completions/attribute.rs21
-rw-r--r--crates/completion/src/completions/dot.rs8
-rw-r--r--crates/completion/src/completions/postfix.rs26
-rw-r--r--crates/completion/src/completions/trait_impl.rs6
-rw-r--r--crates/completion/src/patterns.rs10
-rw-r--r--crates/flycheck/Cargo.toml2
-rw-r--r--crates/hir/src/code_model.rs18
-rw-r--r--crates/hir/src/from_id.rs2
-rw-r--r--crates/hir_def/src/body/lower.rs4
-rw-r--r--crates/hir_def/src/expr.rs2
-rw-r--r--crates/hir_def/src/nameres/collector.rs8
-rw-r--r--crates/hir_def/src/resolver.rs2
-rw-r--r--crates/hir_expand/src/db.rs2
-rw-r--r--crates/hir_ty/src/diagnostics/expr.rs4
-rw-r--r--crates/hir_ty/src/diagnostics/match_check.rs4
-rw-r--r--crates/hir_ty/src/lower.rs6
-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.rs40
-rw-r--r--crates/ide/src/inlay_hints.rs26
-rw-r--r--crates/ide/src/lib.rs2
-rw-r--r--crates/ide/src/references.rs16
-rw-r--r--crates/ide/src/references/rename.rs4
-rw-r--r--crates/ide/src/runnables.rs110
-rw-r--r--crates/ide/src/syntax_highlighting.rs598
-rw-r--r--crates/ide/src/syntax_highlighting/format.rs82
-rw-r--r--crates/ide/src/syntax_highlighting/highlight.rs530
-rw-r--r--crates/ide/src/syntax_highlighting/highlights.rs12
-rw-r--r--crates/ide/src/syntax_highlighting/inject.rs (renamed from crates/ide/src/syntax_highlighting/injection.rs)132
-rw-r--r--crates/ide/src/syntax_highlighting/injector.rs3
-rw-r--r--crates/ide/src/syntax_highlighting/macro_rules.rs2
-rw-r--r--crates/ide/src/syntax_highlighting/tags.rs36
-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.html52
-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_db/src/defs.rs36
-rw-r--r--crates/ide_db/src/imports_locator.rs2
-rw-r--r--crates/ide_db/src/search.rs2
-rw-r--r--crates/mbe/src/lib.rs4
-rw-r--r--crates/mbe/src/mbe_expander/matcher.rs2
-rw-r--r--crates/mbe/src/mbe_expander/transcriber.rs8
-rw-r--r--crates/mbe/src/syntax_bridge.rs12
-rw-r--r--crates/mbe/src/tests.rs2
-rw-r--r--crates/parser/src/grammar/expressions/atom.rs12
-rw-r--r--crates/parser/src/grammar/items.rs26
-rw-r--r--crates/parser/src/grammar/items/traits.rs2
-rw-r--r--crates/parser/src/grammar/items/use_item.rs2
-rw-r--r--crates/parser/src/grammar/patterns.rs2
-rw-r--r--crates/parser/src/grammar/type_args.rs6
-rw-r--r--crates/parser/src/grammar/type_params.rs4
-rw-r--r--crates/proc_macro_api/src/msg.rs2
-rw-r--r--crates/proc_macro_srv/Cargo.toml2
-rw-r--r--crates/proc_macro_srv/src/proc_macro/bridge/rpc.rs2
-rw-r--r--crates/proc_macro_srv/src/rustc_server.rs2
-rw-r--r--crates/project_model/Cargo.toml2
-rw-r--r--crates/rust-analyzer/src/cli/analysis_stats.rs2
-rw-r--r--crates/rust-analyzer/src/config.rs12
-rw-r--r--crates/rust-analyzer/src/global_state.rs3
-rw-r--r--crates/rust-analyzer/src/lib.rs1
-rw-r--r--crates/rust-analyzer/src/lsp_utils.rs10
-rw-r--r--crates/rust-analyzer/src/main_loop.rs82
-rw-r--r--crates/rust-analyzer/src/markdown.rs2
-rw-r--r--crates/rust-analyzer/src/op_queue.rs25
-rw-r--r--crates/rust-analyzer/src/reload.rs69
-rw-r--r--crates/rust-analyzer/src/semantic_tokens.rs8
-rw-r--r--crates/rust-analyzer/src/to_proto.rs18
-rw-r--r--crates/stdx/src/lib.rs4
-rw-r--r--crates/syntax/src/algo.rs4
-rw-r--r--crates/syntax/src/ast/make.rs2
-rw-r--r--crates/syntax/src/ast/node_ext.rs2
-rw-r--r--crates/syntax/src/parsing/lexer.rs8
-rw-r--r--crates/syntax/src/validation.rs6
-rw-r--r--crates/syntax/test_data/parser/inline/ok/0002_use_tree_list.rast98
-rw-r--r--crates/syntax/test_data/parser/inline/ok/0002_use_tree_list.rs2
-rw-r--r--crates/syntax/test_data/parser/inline/ok/0039_type_arg.rast28
-rw-r--r--crates/syntax/test_data/parser/inline/ok/0039_type_arg.rs2
-rw-r--r--crates/test_utils/src/lib.rs2
-rw-r--r--docs/dev/style.md23
-rw-r--r--xtask/tests/tidy.rs36
88 files changed, 1712 insertions, 1349 deletions
diff --git a/Cargo.lock b/Cargo.lock
index bc38c5a37..5563fdeb2 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -127,12 +127,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
127checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" 127checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
128 128
129[[package]] 129[[package]]
130name = "cargo-platform"
131version = "0.1.1"
132source = "registry+https://github.com/rust-lang/crates.io-index"
133checksum = "0226944a63d1bf35a3b5f948dd7c59e263db83695c9e8bffc4037de02e30f1d7"
134dependencies = [
135 "serde",
136]
137
138[[package]]
130name = "cargo_metadata" 139name = "cargo_metadata"
131version = "0.12.0" 140version = "0.12.2"
132source = "registry+https://github.com/rust-lang/crates.io-index" 141source = "registry+https://github.com/rust-lang/crates.io-index"
133checksum = "d5a5f7b42f606b7f23674f6f4d877628350682bc40687d3fae65679a58d55345" 142checksum = "11a47b6286279a9998588ef7050d1ebc2500c69892a557c90fe5d071c64415dc"
134dependencies = [ 143dependencies = [
144 "cargo-platform",
135 "semver", 145 "semver",
146 "semver-parser",
136 "serde", 147 "serde",
137 "serde_json", 148 "serde_json",
138] 149]
diff --git a/crates/assists/src/handlers/remove_dbg.rs b/crates/assists/src/handlers/remove_dbg.rs
index 0320c2f12..6114091f2 100644
--- a/crates/assists/src/handlers/remove_dbg.rs
+++ b/crates/assists/src/handlers/remove_dbg.rs
@@ -1,6 +1,6 @@
1use syntax::{ 1use syntax::{
2 ast::{self, AstNode}, 2 ast::{self, AstNode},
3 match_ast, SyntaxElement, SyntaxKind, TextRange, TextSize, T, 3 match_ast, SyntaxElement, TextRange, TextSize, T,
4}; 4};
5 5
6use crate::{AssistContext, AssistId, AssistKind, Assists}; 6use crate::{AssistContext, AssistId, AssistKind, Assists};
@@ -136,14 +136,14 @@ fn needs_parentheses_around_macro_contents(macro_contents: Vec<SyntaxElement>) -
136 symbol_kind => { 136 symbol_kind => {
137 let symbol_not_in_bracket = unpaired_brackets_in_contents.is_empty(); 137 let symbol_not_in_bracket = unpaired_brackets_in_contents.is_empty();
138 if symbol_not_in_bracket 138 if symbol_not_in_bracket
139 && symbol_kind != SyntaxKind::COLON // paths 139 && symbol_kind != T![:] // paths
140 && (symbol_kind != SyntaxKind::DOT // field/method access 140 && (symbol_kind != T![.] // field/method access
141 || macro_contents // range expressions consist of two SyntaxKind::Dot in macro invocations 141 || macro_contents // range expressions consist of two SyntaxKind::Dot in macro invocations
142 .peek() 142 .peek()
143 .map(|element| element.kind() == SyntaxKind::DOT) 143 .map(|element| element.kind() == T![.])
144 .unwrap_or(false)) 144 .unwrap_or(false))
145 && symbol_kind != SyntaxKind::QUESTION // try operator 145 && symbol_kind != T![?] // try operator
146 && (symbol_kind.is_punct() || symbol_kind == SyntaxKind::AS_KW) 146 && (symbol_kind.is_punct() || symbol_kind == T![as])
147 { 147 {
148 return true; 148 return true;
149 } 149 }
diff --git a/crates/completion/src/completions/attribute.rs b/crates/completion/src/completions/attribute.rs
index 3a29b5203..e5522980d 100644
--- a/crates/completion/src/completions/attribute.rs
+++ b/crates/completion/src/completions/attribute.rs
@@ -5,7 +5,7 @@
5 5
6use itertools::Itertools; 6use itertools::Itertools;
7use rustc_hash::FxHashSet; 7use rustc_hash::FxHashSet;
8use syntax::{ast, AstNode, SyntaxKind}; 8use syntax::{ast, AstNode, T};
9 9
10use crate::{ 10use crate::{
11 context::CompletionContext, 11 context::CompletionContext,
@@ -21,15 +21,17 @@ pub(crate) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext)
21 21
22 let attribute = ctx.attribute_under_caret.as_ref()?; 22 let attribute = ctx.attribute_under_caret.as_ref()?;
23 match (attribute.path(), attribute.token_tree()) { 23 match (attribute.path(), attribute.token_tree()) {
24 (Some(path), Some(token_tree)) => match path.to_string().as_str() { 24 (Some(path), Some(token_tree)) => {
25 "derive" => complete_derive(acc, ctx, token_tree), 25 let path = path.syntax().text();
26 "feature" => complete_lint(acc, ctx, token_tree, FEATURES), 26 if path == "derive" {
27 "allow" | "warn" | "deny" | "forbid" => { 27 complete_derive(acc, ctx, token_tree)
28 } else if path == "feature" {
29 complete_lint(acc, ctx, token_tree, FEATURES)
30 } else if path == "allow" || path == "warn" || path == "deny" || path == "forbid" {
28 complete_lint(acc, ctx, token_tree.clone(), DEFAULT_LINT_COMPLETIONS); 31 complete_lint(acc, ctx, token_tree.clone(), DEFAULT_LINT_COMPLETIONS);
29 complete_lint(acc, ctx, token_tree, CLIPPY_LINTS); 32 complete_lint(acc, ctx, token_tree, CLIPPY_LINTS);
30 } 33 }
31 _ => {} 34 }
32 },
33 (_, Some(_token_tree)) => {} 35 (_, Some(_token_tree)) => {}
34 _ => complete_attribute_start(acc, ctx, attribute), 36 _ => complete_attribute_start(acc, ctx, attribute),
35 } 37 }
@@ -203,8 +205,7 @@ fn complete_lint(
203fn parse_comma_sep_input(derive_input: ast::TokenTree) -> Result<FxHashSet<String>, ()> { 205fn parse_comma_sep_input(derive_input: ast::TokenTree) -> Result<FxHashSet<String>, ()> {
204 match (derive_input.left_delimiter_token(), derive_input.right_delimiter_token()) { 206 match (derive_input.left_delimiter_token(), derive_input.right_delimiter_token()) {
205 (Some(left_paren), Some(right_paren)) 207 (Some(left_paren), Some(right_paren))
206 if left_paren.kind() == SyntaxKind::L_PAREN 208 if left_paren.kind() == T!['('] && right_paren.kind() == T![')'] =>
207 && right_paren.kind() == SyntaxKind::R_PAREN =>
208 { 209 {
209 let mut input_derives = FxHashSet::default(); 210 let mut input_derives = FxHashSet::default();
210 let mut current_derive = String::new(); 211 let mut current_derive = String::new();
@@ -216,7 +217,7 @@ fn parse_comma_sep_input(derive_input: ast::TokenTree) -> Result<FxHashSet<Strin
216 .skip(1) 217 .skip(1)
217 .take_while(|token| token != &right_paren) 218 .take_while(|token| token != &right_paren)
218 { 219 {
219 if SyntaxKind::COMMA == token.kind() { 220 if T![,] == token.kind() {
220 if !current_derive.is_empty() { 221 if !current_derive.is_empty() {
221 input_derives.insert(current_derive); 222 input_derives.insert(current_derive);
222 current_derive = String::new(); 223 current_derive = String::new();
diff --git a/crates/completion/src/completions/dot.rs b/crates/completion/src/completions/dot.rs
index 2e25c8ba2..d04eef65a 100644
--- a/crates/completion/src/completions/dot.rs
+++ b/crates/completion/src/completions/dot.rs
@@ -373,20 +373,20 @@ fn foo(a: A) {
373 fn macro_expansion_resilient() { 373 fn macro_expansion_resilient() {
374 check( 374 check(
375 r#" 375 r#"
376macro_rules! dbg { 376macro_rules! d {
377 () => {}; 377 () => {};
378 ($val:expr) => { 378 ($val:expr) => {
379 match $val { tmp => { tmp } } 379 match $val { tmp => { tmp } }
380 }; 380 };
381 // Trailing comma with single argument is ignored 381 // Trailing comma with single argument is ignored
382 ($val:expr,) => { $crate::dbg!($val) }; 382 ($val:expr,) => { $crate::d!($val) };
383 ($($val:expr),+ $(,)?) => { 383 ($($val:expr),+ $(,)?) => {
384 ($($crate::dbg!($val)),+,) 384 ($($crate::d!($val)),+,)
385 }; 385 };
386} 386}
387struct A { the_field: u32 } 387struct A { the_field: u32 }
388fn foo(a: A) { 388fn foo(a: A) {
389 dbg!(a.$0) 389 d!(a.$0)
390} 390}
391"#, 391"#,
392 expect![[r#" 392 expect![[r#"
diff --git a/crates/completion/src/completions/postfix.rs b/crates/completion/src/completions/postfix.rs
index 87f0c0b2a..9c34ed0b6 100644
--- a/crates/completion/src/completions/postfix.rs
+++ b/crates/completion/src/completions/postfix.rs
@@ -35,11 +35,14 @@ pub(crate) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
35 None => return, 35 None => return,
36 }; 36 };
37 37
38 let ref_removed_ty =
39 std::iter::successors(Some(receiver_ty.clone()), |ty| ty.remove_ref()).last().unwrap();
40
38 let cap = match ctx.config.snippet_cap { 41 let cap = match ctx.config.snippet_cap {
39 Some(it) => it, 42 Some(it) => it,
40 None => return, 43 None => return,
41 }; 44 };
42 let try_enum = TryEnum::from_ty(&ctx.sema, &receiver_ty); 45 let try_enum = TryEnum::from_ty(&ctx.sema, &ref_removed_ty);
43 if let Some(try_enum) = &try_enum { 46 if let Some(try_enum) = &try_enum {
44 match try_enum { 47 match try_enum {
45 TryEnum::Result => { 48 TryEnum::Result => {
@@ -496,6 +499,27 @@ fn main() {
496 fn postfix_completion_for_references() { 499 fn postfix_completion_for_references() {
497 check_edit("dbg", r#"fn main() { &&42.$0 }"#, r#"fn main() { dbg!(&&42) }"#); 500 check_edit("dbg", r#"fn main() { &&42.$0 }"#, r#"fn main() { dbg!(&&42) }"#);
498 check_edit("refm", r#"fn main() { &&42.$0 }"#, r#"fn main() { &&&mut 42 }"#); 501 check_edit("refm", r#"fn main() { &&42.$0 }"#, r#"fn main() { &&&mut 42 }"#);
502 check_edit(
503 "ifl",
504 r#"
505enum Option<T> { Some(T), None }
506
507fn main() {
508 let bar = &Option::Some(true);
509 bar.$0
510}
511"#,
512 r#"
513enum Option<T> { Some(T), None }
514
515fn main() {
516 let bar = &Option::Some(true);
517 if let Some($1) = bar {
518 $0
519}
520}
521"#,
522 )
499 } 523 }
500 524
501 #[test] 525 #[test]
diff --git a/crates/completion/src/completions/trait_impl.rs b/crates/completion/src/completions/trait_impl.rs
index aa9c845da..135ae49dc 100644
--- a/crates/completion/src/completions/trait_impl.rs
+++ b/crates/completion/src/completions/trait_impl.rs
@@ -93,11 +93,11 @@ fn completion_match(ctx: &CompletionContext) -> Option<(ImplCompletionKind, Synt
93 // `impl .. { const $0 }` 93 // `impl .. { const $0 }`
94 // ERROR 0 94 // ERROR 0
95 // CONST_KW <- * 95 // CONST_KW <- *
96 SyntaxKind::CONST_KW => 0, 96 T![const] => 0,
97 // `impl .. { fn/type $0 }` 97 // `impl .. { fn/type $0 }`
98 // FN/TYPE_ALIAS 0 98 // FN/TYPE_ALIAS 0
99 // FN_KW <- * 99 // FN_KW <- *
100 SyntaxKind::FN_KW | SyntaxKind::TYPE_KW => 0, 100 T![fn] | T![type] => 0,
101 // `impl .. { fn/type/const foo$0 }` 101 // `impl .. { fn/type/const foo$0 }`
102 // FN/TYPE_ALIAS/CONST 1 102 // FN/TYPE_ALIAS/CONST 1
103 // NAME 0 103 // NAME 0
@@ -121,7 +121,7 @@ fn completion_match(ctx: &CompletionContext) -> Option<(ImplCompletionKind, Synt
121 let impl_def = ast::Impl::cast(impl_item.parent()?.parent()?)?; 121 let impl_def = ast::Impl::cast(impl_item.parent()?.parent()?)?;
122 let kind = match impl_item.kind() { 122 let kind = match impl_item.kind() {
123 // `impl ... { const $0 fn/type/const }` 123 // `impl ... { const $0 fn/type/const }`
124 _ if token.kind() == SyntaxKind::CONST_KW => ImplCompletionKind::Const, 124 _ if token.kind() == T![const] => ImplCompletionKind::Const,
125 SyntaxKind::CONST | SyntaxKind::ERROR => ImplCompletionKind::Const, 125 SyntaxKind::CONST | SyntaxKind::ERROR => ImplCompletionKind::Const,
126 SyntaxKind::TYPE_ALIAS => ImplCompletionKind::TypeAlias, 126 SyntaxKind::TYPE_ALIAS => ImplCompletionKind::TypeAlias,
127 SyntaxKind::FN => ImplCompletionKind::Fn, 127 SyntaxKind::FN => ImplCompletionKind::Fn,
diff --git a/crates/completion/src/patterns.rs b/crates/completion/src/patterns.rs
index f148b9402..f3ce91dd1 100644
--- a/crates/completion/src/patterns.rs
+++ b/crates/completion/src/patterns.rs
@@ -5,7 +5,7 @@ use syntax::{
5 ast::{self, LoopBodyOwner}, 5 ast::{self, LoopBodyOwner},
6 match_ast, AstNode, Direction, NodeOrToken, SyntaxElement, 6 match_ast, AstNode, Direction, NodeOrToken, SyntaxElement,
7 SyntaxKind::*, 7 SyntaxKind::*,
8 SyntaxNode, SyntaxToken, 8 SyntaxNode, SyntaxToken, T,
9}; 9};
10 10
11#[cfg(test)] 11#[cfg(test)]
@@ -119,7 +119,7 @@ pub(crate) fn unsafe_is_prev(element: SyntaxElement) -> bool {
119 element 119 element
120 .into_token() 120 .into_token()
121 .and_then(|it| previous_non_trivia_token(it)) 121 .and_then(|it| previous_non_trivia_token(it))
122 .filter(|it| it.kind() == UNSAFE_KW) 122 .filter(|it| it.kind() == T![unsafe])
123 .is_some() 123 .is_some()
124} 124}
125#[test] 125#[test]
@@ -131,7 +131,7 @@ pub(crate) fn if_is_prev(element: SyntaxElement) -> bool {
131 element 131 element
132 .into_token() 132 .into_token()
133 .and_then(|it| previous_non_trivia_token(it)) 133 .and_then(|it| previous_non_trivia_token(it))
134 .filter(|it| it.kind() == IF_KW) 134 .filter(|it| it.kind() == T![if])
135 .is_some() 135 .is_some()
136} 136}
137 137
@@ -139,7 +139,7 @@ pub(crate) fn fn_is_prev(element: SyntaxElement) -> bool {
139 element 139 element
140 .into_token() 140 .into_token()
141 .and_then(|it| previous_non_trivia_token(it)) 141 .and_then(|it| previous_non_trivia_token(it))
142 .filter(|it| it.kind() == FN_KW) 142 .filter(|it| it.kind() == T![fn])
143 .is_some() 143 .is_some()
144} 144}
145#[test] 145#[test]
@@ -154,7 +154,7 @@ pub(crate) fn for_is_prev2(element: SyntaxElement) -> bool {
154 .into_token() 154 .into_token()
155 .and_then(|it| previous_non_trivia_token(it)) 155 .and_then(|it| previous_non_trivia_token(it))
156 .and_then(|it| previous_non_trivia_token(it)) 156 .and_then(|it| previous_non_trivia_token(it))
157 .filter(|it| it.kind() == FOR_KW) 157 .filter(|it| it.kind() == T![for])
158 .is_some() 158 .is_some()
159} 159}
160#[test] 160#[test]
diff --git a/crates/flycheck/Cargo.toml b/crates/flycheck/Cargo.toml
index 1bad64a1b..1d19c7886 100644
--- a/crates/flycheck/Cargo.toml
+++ b/crates/flycheck/Cargo.toml
@@ -12,7 +12,7 @@ doctest = false
12[dependencies] 12[dependencies]
13crossbeam-channel = "0.5.0" 13crossbeam-channel = "0.5.0"
14log = "0.4.8" 14log = "0.4.8"
15cargo_metadata = "=0.12.0" 15cargo_metadata = "0.12.2"
16serde_json = "1.0.48" 16serde_json = "1.0.48"
17jod-thread = "0.1.1" 17jod-thread = "0.1.1"
18 18
diff --git a/crates/hir/src/code_model.rs b/crates/hir/src/code_model.rs
index cc1938333..6cbf5cecf 100644
--- a/crates/hir/src/code_model.rs
+++ b/crates/hir/src/code_model.rs
@@ -1263,6 +1263,24 @@ pub enum GenericParam {
1263} 1263}
1264impl_from!(TypeParam, LifetimeParam, ConstParam for GenericParam); 1264impl_from!(TypeParam, LifetimeParam, ConstParam for GenericParam);
1265 1265
1266impl GenericParam {
1267 pub fn module(self, db: &dyn HirDatabase) -> Module {
1268 match self {
1269 GenericParam::TypeParam(it) => it.module(db),
1270 GenericParam::LifetimeParam(it) => it.module(db),
1271 GenericParam::ConstParam(it) => it.module(db),
1272 }
1273 }
1274
1275 pub fn name(self, db: &dyn HirDatabase) -> Name {
1276 match self {
1277 GenericParam::TypeParam(it) => it.name(db),
1278 GenericParam::LifetimeParam(it) => it.name(db),
1279 GenericParam::ConstParam(it) => it.name(db),
1280 }
1281 }
1282}
1283
1266#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] 1284#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1267pub struct TypeParam { 1285pub struct TypeParam {
1268 pub(crate) id: TypeParamId, 1286 pub(crate) id: TypeParamId,
diff --git a/crates/hir/src/from_id.rs b/crates/hir/src/from_id.rs
index 3e47a5e9d..c8c5fecd7 100644
--- a/crates/hir/src/from_id.rs
+++ b/crates/hir/src/from_id.rs
@@ -1,6 +1,6 @@
1//! Utility module for converting between hir_def ids and code_model wrappers. 1//! Utility module for converting between hir_def ids and code_model wrappers.
2//! 2//!
3//! It's unclear if we need this long-term, but it's definitelly useful while we 3//! It's unclear if we need this long-term, but it's definitely useful while we
4//! are splitting the hir. 4//! are splitting the hir.
5 5
6use hir_def::{ 6use hir_def::{
diff --git a/crates/hir_def/src/body/lower.rs b/crates/hir_def/src/body/lower.rs
index 6be1eaade..3dc33f248 100644
--- a/crates/hir_def/src/body/lower.rs
+++ b/crates/hir_def/src/body/lower.rs
@@ -581,7 +581,7 @@ impl ExprCollector<'_> {
581 match res.value { 581 match res.value {
582 Some((mark, expansion)) => { 582 Some((mark, expansion)) => {
583 // FIXME: Statements are too complicated to recover from error for now. 583 // FIXME: Statements are too complicated to recover from error for now.
584 // It is because we don't have any hygenine for local variable expansion right now. 584 // It is because we don't have any hygiene for local variable expansion right now.
585 if T::can_cast(syntax::SyntaxKind::MACRO_STMTS) && res.err.is_some() { 585 if T::can_cast(syntax::SyntaxKind::MACRO_STMTS) && res.err.is_some() {
586 self.expander.exit(self.db, mark); 586 self.expander.exit(self.db, mark);
587 collector(self, None); 587 collector(self, None);
@@ -959,7 +959,7 @@ impl ExprCollector<'_> {
959 959
960 fn collect_tuple_pat(&mut self, args: AstChildren<ast::Pat>) -> (Vec<PatId>, Option<usize>) { 960 fn collect_tuple_pat(&mut self, args: AstChildren<ast::Pat>) -> (Vec<PatId>, Option<usize>) {
961 // Find the location of the `..`, if there is one. Note that we do not 961 // Find the location of the `..`, if there is one. Note that we do not
962 // consider the possiblity of there being multiple `..` here. 962 // consider the possibility of there being multiple `..` here.
963 let ellipsis = args.clone().position(|p| matches!(p, ast::Pat::RestPat(_))); 963 let ellipsis = args.clone().position(|p| matches!(p, ast::Pat::RestPat(_)));
964 // We want to skip the `..` pattern here, since we account for it above. 964 // We want to skip the `..` pattern here, since we account for it above.
965 let args = args 965 let args = args
diff --git a/crates/hir_def/src/expr.rs b/crates/hir_def/src/expr.rs
index 6a481769d..76f5721e5 100644
--- a/crates/hir_def/src/expr.rs
+++ b/crates/hir_def/src/expr.rs
@@ -1,6 +1,6 @@
1//! This module describes hir-level representation of expressions. 1//! This module describes hir-level representation of expressions.
2//! 2//!
3//! This representaion is: 3//! This representation is:
4//! 4//!
5//! 1. Identity-based. Each expression has an `id`, so we can distinguish 5//! 1. Identity-based. Each expression has an `id`, so we can distinguish
6//! between different `1` in `1 + 1`. 6//! between different `1` in `1 + 1`.
diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs
index 77017e4ea..f027fd48d 100644
--- a/crates/hir_def/src/nameres/collector.rs
+++ b/crates/hir_def/src/nameres/collector.rs
@@ -267,7 +267,7 @@ impl DefCollector<'_> {
267 267
268 // Resolve all indeterminate resolved imports again 268 // Resolve all indeterminate resolved imports again
269 // As some of the macros will expand newly import shadowing partial resolved imports 269 // As some of the macros will expand newly import shadowing partial resolved imports
270 // FIXME: We maybe could skip this, if we handle the Indetermine imports in `resolve_imports` 270 // FIXME: We maybe could skip this, if we handle the indeterminate imports in `resolve_imports`
271 // correctly 271 // correctly
272 let partial_resolved = self.resolved_imports.iter().filter_map(|directive| { 272 let partial_resolved = self.resolved_imports.iter().filter_map(|directive| {
273 if let PartialResolvedImport::Indeterminate(_) = directive.status { 273 if let PartialResolvedImport::Indeterminate(_) = directive.status {
@@ -402,7 +402,7 @@ impl DefCollector<'_> {
402 402
403 /// Define a proc macro 403 /// Define a proc macro
404 /// 404 ///
405 /// A proc macro is similar to normal macro scope, but it would not visiable in legacy textual scoped. 405 /// A proc macro is similar to normal macro scope, but it would not visible in legacy textual scoped.
406 /// And unconditionally exported. 406 /// And unconditionally exported.
407 fn define_proc_macro(&mut self, name: Name, macro_: MacroDefId) { 407 fn define_proc_macro(&mut self, name: Name, macro_: MacroDefId) {
408 self.update( 408 self.update(
@@ -592,7 +592,7 @@ impl DefCollector<'_> {
592 // XXX: urgh, so this works by accident! Here, we look at 592 // XXX: urgh, so this works by accident! Here, we look at
593 // the enum data, and, in theory, this might require us to 593 // the enum data, and, in theory, this might require us to
594 // look back at the crate_def_map, creating a cycle. For 594 // look back at the crate_def_map, creating a cycle. For
595 // example, `enum E { crate::some_macro!(); }`. Luckely, the 595 // example, `enum E { crate::some_macro!(); }`. Luckily, the
596 // only kind of macro that is allowed inside enum is a 596 // only kind of macro that is allowed inside enum is a
597 // `cfg_macro`, and we don't need to run name resolution for 597 // `cfg_macro`, and we don't need to run name resolution for
598 // it, but this is sheer luck! 598 // it, but this is sheer luck!
@@ -655,7 +655,7 @@ impl DefCollector<'_> {
655 &mut self, 655 &mut self,
656 module_id: LocalModuleId, 656 module_id: LocalModuleId,
657 resolutions: &[(Option<Name>, PerNs)], 657 resolutions: &[(Option<Name>, PerNs)],
658 // All resolutions are imported with this visibility; the visibilies in 658 // All resolutions are imported with this visibility; the visibilities in
659 // the `PerNs` values are ignored and overwritten 659 // the `PerNs` values are ignored and overwritten
660 vis: Visibility, 660 vis: Visibility,
661 import_type: ImportType, 661 import_type: ImportType,
diff --git a/crates/hir_def/src/resolver.rs b/crates/hir_def/src/resolver.rs
index 129f1dbac..e4152a0be 100644
--- a/crates/hir_def/src/resolver.rs
+++ b/crates/hir_def/src/resolver.rs
@@ -27,7 +27,7 @@ use crate::{
27 27
28#[derive(Debug, Clone, Default)] 28#[derive(Debug, Clone, Default)]
29pub struct Resolver { 29pub struct Resolver {
30 // FIXME: all usages generally call `.rev`, so maybe reverse once in consturciton? 30 // FIXME: all usages generally call `.rev`, so maybe reverse once in construction?
31 scopes: Vec<Scope>, 31 scopes: Vec<Scope>,
32} 32}
33 33
diff --git a/crates/hir_expand/src/db.rs b/crates/hir_expand/src/db.rs
index ab2637b8c..c62086390 100644
--- a/crates/hir_expand/src/db.rs
+++ b/crates/hir_expand/src/db.rs
@@ -40,7 +40,7 @@ impl TokenExpander {
40 // FIXME switch these to ExpandResult as well 40 // FIXME switch these to ExpandResult as well
41 TokenExpander::BuiltinDerive(it) => it.expand(db, id, tt).into(), 41 TokenExpander::BuiltinDerive(it) => it.expand(db, id, tt).into(),
42 TokenExpander::ProcMacro(_) => { 42 TokenExpander::ProcMacro(_) => {
43 // We store the result in salsa db to prevent non-determinisc behavior in 43 // We store the result in salsa db to prevent non-deterministic behavior in
44 // some proc-macro implementation 44 // some proc-macro implementation
45 // See #4315 for details 45 // See #4315 for details
46 db.expand_proc_macro(id.into()).into() 46 db.expand_proc_macro(id.into()).into()
diff --git a/crates/hir_ty/src/diagnostics/expr.rs b/crates/hir_ty/src/diagnostics/expr.rs
index a1c484fdf..107417c27 100644
--- a/crates/hir_ty/src/diagnostics/expr.rs
+++ b/crates/hir_ty/src/diagnostics/expr.rs
@@ -379,7 +379,7 @@ pub fn record_literal_missing_fields(
379 id: ExprId, 379 id: ExprId,
380 expr: &Expr, 380 expr: &Expr,
381) -> Option<(VariantId, Vec<LocalFieldId>, /*exhaustive*/ bool)> { 381) -> Option<(VariantId, Vec<LocalFieldId>, /*exhaustive*/ bool)> {
382 let (fields, exhausitve) = match expr { 382 let (fields, exhaustive) = match expr {
383 Expr::RecordLit { path: _, fields, spread } => (fields, spread.is_none()), 383 Expr::RecordLit { path: _, fields, spread } => (fields, spread.is_none()),
384 _ => return None, 384 _ => return None,
385 }; 385 };
@@ -400,7 +400,7 @@ pub fn record_literal_missing_fields(
400 if missed_fields.is_empty() { 400 if missed_fields.is_empty() {
401 return None; 401 return None;
402 } 402 }
403 Some((variant_def, missed_fields, exhausitve)) 403 Some((variant_def, missed_fields, exhaustive))
404} 404}
405 405
406pub fn record_pattern_missing_fields( 406pub fn record_pattern_missing_fields(
diff --git a/crates/hir_ty/src/diagnostics/match_check.rs b/crates/hir_ty/src/diagnostics/match_check.rs
index 62c329731..61c47eec8 100644
--- a/crates/hir_ty/src/diagnostics/match_check.rs
+++ b/crates/hir_ty/src/diagnostics/match_check.rs
@@ -14,7 +14,7 @@
14//! The algorithm implemented here is a modified version of the one described in 14//! The algorithm implemented here is a modified version of the one described in
15//! <http://moscova.inria.fr/~maranget/papers/warn/index.html>. 15//! <http://moscova.inria.fr/~maranget/papers/warn/index.html>.
16//! However, to save future implementors from reading the original paper, we 16//! However, to save future implementors from reading the original paper, we
17//! summarise the algorithm here to hopefully save time and be a little clearer 17//! summarize the algorithm here to hopefully save time and be a little clearer
18//! (without being so rigorous). 18//! (without being so rigorous).
19//! 19//!
20//! The core of the algorithm revolves about a "usefulness" check. In particular, we 20//! The core of the algorithm revolves about a "usefulness" check. In particular, we
@@ -132,7 +132,7 @@
132//! The algorithm is inductive (on the number of columns: i.e., components of tuple patterns). 132//! The algorithm is inductive (on the number of columns: i.e., components of tuple patterns).
133//! That means we're going to check the components from left-to-right, so the algorithm 133//! That means we're going to check the components from left-to-right, so the algorithm
134//! operates principally on the first component of the matrix and new pattern-stack `p`. 134//! operates principally on the first component of the matrix and new pattern-stack `p`.
135//! This algorithm is realised in the `is_useful` function. 135//! This algorithm is realized in the `is_useful` function.
136//! 136//!
137//! Base case (`n = 0`, i.e., an empty tuple pattern): 137//! Base case (`n = 0`, i.e., an empty tuple pattern):
138//! - If `P` already contains an empty pattern (i.e., if the number of patterns `m > 0`), then 138//! - If `P` already contains an empty pattern (i.e., if the number of patterns `m > 0`), then
diff --git a/crates/hir_ty/src/lower.rs b/crates/hir_ty/src/lower.rs
index 222f61a11..9594cce8b 100644
--- a/crates/hir_ty/src/lower.rs
+++ b/crates/hir_ty/src/lower.rs
@@ -491,16 +491,16 @@ impl Ty {
491 fn from_hir_path_inner( 491 fn from_hir_path_inner(
492 ctx: &TyLoweringContext<'_>, 492 ctx: &TyLoweringContext<'_>,
493 segment: PathSegment<'_>, 493 segment: PathSegment<'_>,
494 typable: TyDefId, 494 typeable: TyDefId,
495 infer_args: bool, 495 infer_args: bool,
496 ) -> Ty { 496 ) -> Ty {
497 let generic_def = match typable { 497 let generic_def = match typeable {
498 TyDefId::BuiltinType(_) => None, 498 TyDefId::BuiltinType(_) => None,
499 TyDefId::AdtId(it) => Some(it.into()), 499 TyDefId::AdtId(it) => Some(it.into()),
500 TyDefId::TypeAliasId(it) => Some(it.into()), 500 TyDefId::TypeAliasId(it) => Some(it.into()),
501 }; 501 };
502 let substs = substs_from_path_segment(ctx, segment, generic_def, infer_args); 502 let substs = substs_from_path_segment(ctx, segment, generic_def, infer_args);
503 ctx.db.ty(typable).subst(&substs) 503 ctx.db.ty(typeable).subst(&substs)
504 } 504 }
505 505
506 /// Collect generic arguments from a path into a `Substs`. See also 506 /// Collect generic arguments from a path into a `Substs`. See also
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 88d215129..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
@@ -187,7 +187,7 @@ fn runnable_action(
187 Definition::ModuleDef(it) => match it { 187 Definition::ModuleDef(it) => match it {
188 ModuleDef::Module(it) => match it.definition_source(sema.db).value { 188 ModuleDef::Module(it) => match it.definition_source(sema.db).value {
189 ModuleSource::Module(it) => { 189 ModuleSource::Module(it) => {
190 runnable(&sema, it.syntax().clone()).map(|it| HoverAction::Runnable(it)) 190 runnable_mod(&sema, it).map(|it| HoverAction::Runnable(it))
191 } 191 }
192 _ => None, 192 _ => None,
193 }, 193 },
@@ -215,12 +215,12 @@ fn goto_type_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
215 } 215 }
216 }; 216 };
217 217
218 if let Definition::TypeParam(it) = def { 218 if let Definition::GenericParam(GenericParam::TypeParam(it)) = def {
219 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()));
220 } else { 220 } else {
221 let ty = match def { 221 let ty = match def {
222 Definition::Local(it) => it.ty(db), 222 Definition::Local(it) => it.ty(db),
223 Definition::ConstParam(it) => it.ty(db), 223 Definition::GenericParam(GenericParam::ConstParam(it)) => it.ty(db),
224 _ => return None, 224 _ => return None,
225 }; 225 };
226 226
@@ -352,9 +352,11 @@ fn hover_for_definition(db: &RootDatabase, def: Definition) -> Option<Markup> {
352 }) 352 })
353 } 353 }
354 Definition::Label(it) => Some(Markup::fenced_block(&it.name(db))), 354 Definition::Label(it) => Some(Markup::fenced_block(&it.name(db))),
355 Definition::LifetimeParam(it) => Some(Markup::fenced_block(&it.name(db))), 355 Definition::GenericParam(it) => match it {
356 Definition::TypeParam(type_param) => Some(Markup::fenced_block(&type_param.display(db))), 356 GenericParam::TypeParam(it) => Some(Markup::fenced_block(&it.display(db))),
357 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 },
358 }; 360 };
359 361
360 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>
@@ -1946,16 +1948,16 @@ struct S {
1946/// Test cases: 1948/// Test cases:
1947/// case 1. bare URL: https://www.example.com/ 1949/// case 1. bare URL: https://www.example.com/
1948/// case 2. inline URL with title: [example](https://www.example.com/) 1950/// case 2. inline URL with title: [example](https://www.example.com/)
1949/// case 3. code refrence: [`Result`] 1951/// case 3. code reference: [`Result`]
1950/// case 4. code refrence but miss footnote: [`String`] 1952/// case 4. code reference but miss footnote: [`String`]
1951/// case 5. autolink: <http://www.example.com/> 1953/// case 5. autolink: <http://www.example.com/>
1952/// case 6. email address: <[email protected]> 1954/// case 6. email address: <[email protected]>
1953/// case 7. refrence: [example][example] 1955/// case 7. reference: [example][example]
1954/// case 8. collapsed link: [example][] 1956/// case 8. collapsed link: [example][]
1955/// case 9. shortcut link: [example] 1957/// case 9. shortcut link: [example]
1956/// case 10. inline without URL: [example]() 1958/// case 10. inline without URL: [example]()
1957/// case 11. refrence: [foo][foo] 1959/// case 11. reference: [foo][foo]
1958/// case 12. refrence: [foo][bar] 1960/// case 12. reference: [foo][bar]
1959/// case 13. collapsed link: [foo][] 1961/// case 13. collapsed link: [foo][]
1960/// case 14. shortcut link: [foo] 1962/// case 14. shortcut link: [foo]
1961/// case 15. inline without URL: [foo]() 1963/// case 15. inline without URL: [foo]()
@@ -1982,16 +1984,16 @@ pub fn fo$0o() {}
1982 Test cases: 1984 Test cases:
1983 case 1. bare URL: https://www.example.com/ 1985 case 1. bare URL: https://www.example.com/
1984 case 2. inline URL with title: [example](https://www.example.com/) 1986 case 2. inline URL with title: [example](https://www.example.com/)
1985 case 3. code refrence: `Result` 1987 case 3. code reference: `Result`
1986 case 4. code refrence but miss footnote: `String` 1988 case 4. code reference but miss footnote: `String`
1987 case 5. autolink: http://www.example.com/ 1989 case 5. autolink: http://www.example.com/
1988 case 6. email address: [email protected] 1990 case 6. email address: [email protected]
1989 case 7. refrence: example 1991 case 7. reference: example
1990 case 8. collapsed link: example 1992 case 8. collapsed link: example
1991 case 9. shortcut link: example 1993 case 9. shortcut link: example
1992 case 10. inline without URL: example 1994 case 10. inline without URL: example
1993 case 11. refrence: foo 1995 case 11. reference: foo
1994 case 12. refrence: foo 1996 case 12. reference: foo
1995 case 13. collapsed link: foo 1997 case 13. collapsed link: foo
1996 case 14. shortcut link: foo 1998 case 14. shortcut link: foo
1997 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 a74829cd0..3e9a65d9c 100644
--- a/crates/ide/src/inlay_hints.rs
+++ b/crates/ide/src/inlay_hints.rs
@@ -353,13 +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(mut repr) => { 356 Some(argument_string) => {
357 let param_name = param_name.to_ascii_lowercase(); 357 let num_leading_underscores =
358 let argument_string = { 358 argument_string.bytes().take_while(|&c| c == b'_').count();
359 repr.make_ascii_lowercase(); 359
360 repr.trim_start_matches('_') 360 // Does the argument name begin with the parameter name? Ignore leading underscores.
361 }; 361 let mut arg_bytes = argument_string.bytes().skip(num_leading_underscores);
362 argument_string.starts_with(&param_name) || argument_string.ends_with(&param_name) 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 )
363 } 375 }
364 } 376 }
365} 377}
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index 409507bd0..1f368cbd0 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -76,7 +76,7 @@ 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, HlMod, HlMods, HlTag}, 79 tags::{Highlight, HlMod, HlMods, HlPunct, HlTag},
80 HlRange, 80 HlRange,
81 }, 81 },
82}; 82};
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs
index c95ed669c..b774a2be1 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
@@ -21,7 +21,7 @@ use ide_db::{
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};
@@ -130,7 +130,10 @@ 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
@@ -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,7 @@ 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 self_token = syntax.token_at_offset(position.offset).find(|t| t.kind() == T![self])?;
256 syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW)?;
257 let parent = self_token.parent(); 259 let parent = self_token.parent();
258 match_ast! { 260 match_ast! {
259 match parent { 261 match parent {
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs
index 44dfa9a0e..3edc43e08 100644
--- a/crates/ide/src/references/rename.rs
+++ b/crates/ide/src/references/rename.rs
@@ -1005,7 +1005,7 @@ use crate::foo$0::FooContent;
1005//- /lib.rs 1005//- /lib.rs
1006mod fo$0o; 1006mod fo$0o;
1007//- /foo/mod.rs 1007//- /foo/mod.rs
1008// emtpy 1008// empty
1009"#, 1009"#,
1010 expect![[r#" 1010 expect![[r#"
1011 RangeInfo { 1011 RangeInfo {
@@ -1055,7 +1055,7 @@ mod fo$0o;
1055mod outer { mod fo$0o; } 1055mod outer { mod fo$0o; }
1056 1056
1057//- /outer/foo.rs 1057//- /outer/foo.rs
1058// emtpy 1058// empty
1059"#, 1059"#,
1060 expect![[r#" 1060 expect![[r#"
1061 RangeInfo { 1061 RangeInfo {
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 ad456bc00..f2d4da78d 100644
--- a/crates/ide/src/syntax_highlighting.rs
+++ b/crates/ide/src/syntax_highlighting.rs
@@ -3,30 +3,29 @@ pub(crate) mod tags;
3mod highlights; 3mod highlights;
4mod injector; 4mod injector;
5 5
6mod highlight;
6mod format; 7mod format;
7mod injection;
8mod macro_rules; 8mod macro_rules;
9mod inject;
9 10
10mod html; 11mod html;
11#[cfg(test)] 12#[cfg(test)]
12mod tests; 13mod tests;
13 14
14use hir::{AsAssocItem, Local, Name, Semantics, VariantDef}; 15use hir::{Name, Semantics};
15use ide_db::{ 16use ide_db::RootDatabase;
16 defs::{Definition, NameClass, NameRefClass},
17 RootDatabase,
18};
19use rustc_hash::FxHashMap; 17use rustc_hash::FxHashMap;
20use syntax::{ 18use syntax::{
21 ast::{self, HasFormatSpecifier}, 19 ast::{self, HasFormatSpecifier},
22 AstNode, AstToken, Direction, NodeOrToken, SyntaxElement, 20 AstNode, AstToken, Direction, NodeOrToken,
23 SyntaxKind::{self, *}, 21 SyntaxKind::*,
24 SyntaxNode, SyntaxToken, TextRange, WalkEvent, T, 22 SyntaxNode, TextRange, WalkEvent, T,
25}; 23};
26 24
27use crate::{ 25use crate::{
28 syntax_highlighting::{ 26 syntax_highlighting::{
29 format::FormatStringHighlighter, macro_rules::MacroRulesHighlighter, tags::Highlight, 27 format::highlight_format_string, highlights::Highlights,
28 macro_rules::MacroRulesHighlighter, tags::Highlight,
30 }, 29 },
31 FileId, HlMod, HlTag, SymbolKind, 30 FileId, HlMod, HlTag, SymbolKind,
32}; 31};
@@ -73,12 +72,22 @@ pub(crate) fn highlight(
73 } 72 }
74 }; 73 };
75 74
76 let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default();
77 let mut hl = highlights::Highlights::new(range_to_highlight); 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) {
87 let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default();
78 88
79 let mut current_macro_call: Option<ast::MacroCall> = None; 89 let mut current_macro_call: Option<ast::MacroCall> = None;
80 let mut current_macro_rules: Option<ast::MacroRules> = None; 90 let mut current_macro_rules: Option<ast::MacroRules> = None;
81 let mut format_string_highlighter = FormatStringHighlighter::default();
82 let mut macro_rules_highlighter = MacroRulesHighlighter::default(); 91 let mut macro_rules_highlighter = MacroRulesHighlighter::default();
83 let mut inside_attribute = false; 92 let mut inside_attribute = false;
84 93
@@ -110,7 +119,6 @@ pub(crate) fn highlight(
110 WalkEvent::Leave(Some(mc)) => { 119 WalkEvent::Leave(Some(mc)) => {
111 assert_eq!(current_macro_call, Some(mc)); 120 assert_eq!(current_macro_call, Some(mc));
112 current_macro_call = None; 121 current_macro_call = None;
113 format_string_highlighter = FormatStringHighlighter::default();
114 } 122 }
115 _ => (), 123 _ => (),
116 } 124 }
@@ -128,26 +136,24 @@ pub(crate) fn highlight(
128 } 136 }
129 _ => (), 137 _ => (),
130 } 138 }
131
132 match &event { 139 match &event {
133 // Check for Rust code in documentation
134 WalkEvent::Leave(NodeOrToken::Node(node)) => {
135 if ast::Attr::can_cast(node.kind()) {
136 inside_attribute = false
137 }
138 if let Some((new_comments, inj)) = injection::extract_doc_comments(node) {
139 injection::highlight_doc_comment(new_comments, inj, &mut hl);
140 }
141 }
142 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()) => {
143 inside_attribute = true 141 inside_attribute = true
144 } 142 }
143 WalkEvent::Leave(NodeOrToken::Node(node)) if ast::Attr::can_cast(node.kind()) => {
144 inside_attribute = false
145 }
145 _ => (), 146 _ => (),
146 } 147 }
147 148
148 let element = match event { 149 let element = match event {
149 WalkEvent::Enter(it) => it, 150 WalkEvent::Enter(it) => it,
150 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 }
151 }; 157 };
152 158
153 let range = element.text_range(); 159 let range = element.text_range();
@@ -167,8 +173,6 @@ pub(crate) fn highlight(
167 let token = sema.descend_into_macros(token.clone()); 173 let token = sema.descend_into_macros(token.clone());
168 let parent = token.parent(); 174 let parent = token.parent();
169 175
170 format_string_highlighter.check_for_format_string(&parent);
171
172 // We only care Name and Name_ref 176 // We only care Name and Name_ref
173 match (token.kind(), parent.kind()) { 177 match (token.kind(), parent.kind()) {
174 (IDENT, NAME) | (IDENT, NAME_REF) => parent.into(), 178 (IDENT, NAME) | (IDENT, NAME_REF) => parent.into(),
@@ -181,13 +185,17 @@ pub(crate) fn highlight(
181 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) {
182 if token.is_raw() { 186 if token.is_raw() {
183 let expanded = element_to_highlight.as_token().unwrap().clone(); 187 let expanded = element_to_highlight.as_token().unwrap().clone();
184 if injection::highlight_injection(&mut hl, &sema, token, expanded).is_some() { 188 if inject::ra_fixture(hl, &sema, token, expanded).is_some() {
185 continue; 189 continue;
186 } 190 }
187 } 191 }
188 } 192 }
189 193
190 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(
191 &sema, 199 &sema,
192 &mut bindings_shadow_count, 200 &mut bindings_shadow_count,
193 syntactic_name_ref_highlighting, 201 syntactic_name_ref_highlighting,
@@ -197,31 +205,25 @@ pub(crate) fn highlight(
197 highlight = highlight | HlMod::Attribute; 205 highlight = highlight | HlMod::Attribute;
198 } 206 }
199 207
200 if macro_rules_highlighter.highlight(element_to_highlight.clone()).is_none() { 208 hl.add(HlRange { range, highlight, binding_hash });
201 hl.add(HlRange { range, highlight, binding_hash }); 209 }
202 }
203 210
204 if let Some(string) = 211 if let Some(string) = element_to_highlight.as_token().cloned().and_then(ast::String::cast) {
205 element_to_highlight.as_token().cloned().and_then(ast::String::cast) 212 highlight_format_string(hl, &string, range);
206 { 213 // Highlight escape sequences
207 format_string_highlighter.highlight_format_string(&mut hl, &string, range); 214 if let Some(char_ranges) = string.char_ranges() {
208 // Highlight escape sequences 215 for (piece_range, _) in char_ranges.iter().filter(|(_, char)| char.is_ok()) {
209 if let Some(char_ranges) = string.char_ranges() { 216 if string.text()[piece_range.start().into()..].starts_with('\\') {
210 for (piece_range, _) in char_ranges.iter().filter(|(_, char)| char.is_ok()) { 217 hl.add(HlRange {
211 if string.text()[piece_range.start().into()..].starts_with('\\') { 218 range: piece_range + range.start(),
212 hl.add(HlRange { 219 highlight: HlTag::EscapeSequence.into(),
213 range: piece_range + range.start(), 220 binding_hash: None,
214 highlight: HlTag::EscapeSequence.into(), 221 });
215 binding_hash: None,
216 });
217 }
218 } 222 }
219 } 223 }
220 } 224 }
221 } 225 }
222 } 226 }
223
224 hl.to_vec()
225} 227}
226 228
227fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> { 229fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> {
@@ -239,505 +241,3 @@ fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> {
239 241
240 Some(TextRange::new(range_start, range_end)) 242 Some(TextRange::new(range_start, range_end))
241} 243}
242
243/// Returns true if the parent nodes of `node` all match the `SyntaxKind`s in `kinds` exactly.
244fn parents_match(mut node: NodeOrToken<SyntaxNode, SyntaxToken>, mut kinds: &[SyntaxKind]) -> bool {
245 while let (Some(parent), [kind, rest @ ..]) = (&node.parent(), kinds) {
246 if parent.kind() != *kind {
247 return false;
248 }
249
250 // FIXME: Would be nice to get parent out of the match, but binding by-move and by-value
251 // in the same pattern is unstable: rust-lang/rust#68354.
252 node = node.parent().unwrap().into();
253 kinds = rest;
254 }
255
256 // Only true if we matched all expected kinds
257 kinds.len() == 0
258}
259
260fn is_consumed_lvalue(
261 node: NodeOrToken<SyntaxNode, SyntaxToken>,
262 local: &Local,
263 db: &RootDatabase,
264) -> bool {
265 // When lvalues are passed as arguments and they're not Copy, then mark them as Consuming.
266 parents_match(node, &[PATH_SEGMENT, PATH, PATH_EXPR, ARG_LIST]) && !local.ty(db).is_copy(db)
267}
268
269fn highlight_element(
270 sema: &Semantics<RootDatabase>,
271 bindings_shadow_count: &mut FxHashMap<Name, u32>,
272 syntactic_name_ref_highlighting: bool,
273 element: SyntaxElement,
274) -> Option<(Highlight, Option<u64>)> {
275 let db = sema.db;
276 let mut binding_hash = None;
277 let highlight: Highlight = match element.kind() {
278 FN => {
279 bindings_shadow_count.clear();
280 return None;
281 }
282
283 // Highlight definitions depending on the "type" of the definition.
284 NAME => {
285 let name = element.into_node().and_then(ast::Name::cast).unwrap();
286 let name_kind = NameClass::classify(sema, &name);
287
288 if let Some(NameClass::Definition(Definition::Local(local))) = &name_kind {
289 if let Some(name) = local.name(db) {
290 let shadow_count = bindings_shadow_count.entry(name.clone()).or_default();
291 *shadow_count += 1;
292 binding_hash = Some(calc_binding_hash(&name, *shadow_count))
293 }
294 };
295
296 match name_kind {
297 Some(NameClass::ExternCrate(_)) => HlTag::Symbol(SymbolKind::Module).into(),
298 Some(NameClass::Definition(def)) => highlight_def(db, def) | HlMod::Definition,
299 Some(NameClass::ConstReference(def)) => highlight_def(db, def),
300 Some(NameClass::PatFieldShorthand { field_ref, .. }) => {
301 let mut h = HlTag::Symbol(SymbolKind::Field).into();
302 if let Definition::Field(field) = field_ref {
303 if let VariantDef::Union(_) = field.parent_def(db) {
304 h |= HlMod::Unsafe;
305 }
306 }
307
308 h
309 }
310 None => highlight_name_by_syntax(name) | HlMod::Definition,
311 }
312 }
313
314 // Highlight references like the definitions they resolve to
315 NAME_REF if element.ancestors().any(|it| it.kind() == ATTR) => {
316 // even though we track whether we are in an attribute or not we still need this special case
317 // as otherwise we would emit unresolved references for name refs inside attributes
318 Highlight::from(HlTag::Symbol(SymbolKind::Function))
319 }
320 NAME_REF => {
321 let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap();
322 highlight_func_by_name_ref(sema, &name_ref).unwrap_or_else(|| {
323 match NameRefClass::classify(sema, &name_ref) {
324 Some(name_kind) => match name_kind {
325 NameRefClass::ExternCrate(_) => HlTag::Symbol(SymbolKind::Module).into(),
326 NameRefClass::Definition(def) => {
327 if let Definition::Local(local) = &def {
328 if let Some(name) = local.name(db) {
329 let shadow_count =
330 bindings_shadow_count.entry(name.clone()).or_default();
331 binding_hash = Some(calc_binding_hash(&name, *shadow_count))
332 }
333 };
334
335 let mut h = highlight_def(db, def);
336
337 if let Definition::Local(local) = &def {
338 if is_consumed_lvalue(name_ref.syntax().clone().into(), local, db) {
339 h |= HlMod::Consuming;
340 }
341 }
342
343 if let Some(parent) = name_ref.syntax().parent() {
344 if matches!(parent.kind(), FIELD_EXPR | RECORD_PAT_FIELD) {
345 if let Definition::Field(field) = def {
346 if let VariantDef::Union(_) = field.parent_def(db) {
347 h |= HlMod::Unsafe;
348 }
349 }
350 }
351 }
352
353 h
354 }
355 NameRefClass::FieldShorthand { .. } => {
356 HlTag::Symbol(SymbolKind::Field).into()
357 }
358 },
359 None if syntactic_name_ref_highlighting => {
360 highlight_name_ref_by_syntax(name_ref, sema)
361 }
362 None => HlTag::UnresolvedReference.into(),
363 }
364 })
365 }
366
367 // Simple token-based highlighting
368 COMMENT => {
369 let comment = element.into_token().and_then(ast::Comment::cast)?;
370 let h = HlTag::Comment;
371 match comment.kind().doc {
372 Some(_) => h | HlMod::Documentation,
373 None => h.into(),
374 }
375 }
376 STRING | BYTE_STRING => HlTag::StringLiteral.into(),
377 ATTR => HlTag::Attribute.into(),
378 INT_NUMBER | FLOAT_NUMBER => HlTag::NumericLiteral.into(),
379 BYTE => HlTag::ByteLiteral.into(),
380 CHAR => HlTag::CharLiteral.into(),
381 QUESTION => Highlight::new(HlTag::Operator) | HlMod::ControlFlow,
382 LIFETIME => {
383 let lifetime = element.into_node().and_then(ast::Lifetime::cast).unwrap();
384
385 match NameClass::classify_lifetime(sema, &lifetime) {
386 Some(NameClass::Definition(def)) => highlight_def(db, def) | HlMod::Definition,
387 None => match NameRefClass::classify_lifetime(sema, &lifetime) {
388 Some(NameRefClass::Definition(def)) => highlight_def(db, def),
389 _ => Highlight::new(HlTag::Symbol(SymbolKind::LifetimeParam)),
390 },
391 _ => Highlight::new(HlTag::Symbol(SymbolKind::LifetimeParam)) | HlMod::Definition,
392 }
393 }
394 p if p.is_punct() => match p {
395 T![&] => {
396 let h = HlTag::Operator.into();
397 let is_unsafe = element
398 .parent()
399 .and_then(ast::RefExpr::cast)
400 .map(|ref_expr| sema.is_unsafe_ref_expr(&ref_expr))
401 .unwrap_or(false);
402 if is_unsafe {
403 h | HlMod::Unsafe
404 } else {
405 h
406 }
407 }
408 T![::] | T![->] | T![=>] | T![..] | T![=] | T![@] | T![.] => HlTag::Operator.into(),
409 T![!] if element.parent().and_then(ast::MacroCall::cast).is_some() => {
410 HlTag::Symbol(SymbolKind::Macro).into()
411 }
412 T![!] if element.parent().and_then(ast::NeverType::cast).is_some() => {
413 HlTag::BuiltinType.into()
414 }
415 T![*] if element.parent().and_then(ast::PtrType::cast).is_some() => {
416 HlTag::Keyword.into()
417 }
418 T![*] if element.parent().and_then(ast::PrefixExpr::cast).is_some() => {
419 let prefix_expr = element.parent().and_then(ast::PrefixExpr::cast)?;
420
421 let expr = prefix_expr.expr()?;
422 let ty = sema.type_of_expr(&expr)?;
423 if ty.is_raw_ptr() {
424 HlTag::Operator | HlMod::Unsafe
425 } else if let Some(ast::PrefixOp::Deref) = prefix_expr.op_kind() {
426 HlTag::Operator.into()
427 } else {
428 HlTag::Punctuation.into()
429 }
430 }
431 T![-] if element.parent().and_then(ast::PrefixExpr::cast).is_some() => {
432 let prefix_expr = element.parent().and_then(ast::PrefixExpr::cast)?;
433
434 let expr = prefix_expr.expr()?;
435 match expr {
436 ast::Expr::Literal(_) => HlTag::NumericLiteral,
437 _ => HlTag::Operator,
438 }
439 .into()
440 }
441 _ if element.parent().and_then(ast::PrefixExpr::cast).is_some() => {
442 HlTag::Operator.into()
443 }
444 _ if element.parent().and_then(ast::BinExpr::cast).is_some() => HlTag::Operator.into(),
445 _ if element.parent().and_then(ast::RangeExpr::cast).is_some() => {
446 HlTag::Operator.into()
447 }
448 _ if element.parent().and_then(ast::RangePat::cast).is_some() => HlTag::Operator.into(),
449 _ if element.parent().and_then(ast::RestPat::cast).is_some() => HlTag::Operator.into(),
450 _ if element.parent().and_then(ast::Attr::cast).is_some() => HlTag::Attribute.into(),
451 _ => HlTag::Punctuation.into(),
452 },
453
454 k if k.is_keyword() => {
455 let h = Highlight::new(HlTag::Keyword);
456 match k {
457 T![break]
458 | T![continue]
459 | T![else]
460 | T![if]
461 | T![loop]
462 | T![match]
463 | T![return]
464 | T![while]
465 | T![in] => h | HlMod::ControlFlow,
466 T![for] if !is_child_of_impl(&element) => h | HlMod::ControlFlow,
467 T![unsafe] => h | HlMod::Unsafe,
468 T![true] | T![false] => HlTag::BoolLiteral.into(),
469 T![self] => {
470 let self_param_is_mut = element
471 .parent()
472 .and_then(ast::SelfParam::cast)
473 .and_then(|p| p.mut_token())
474 .is_some();
475 let self_path = &element
476 .parent()
477 .as_ref()
478 .and_then(SyntaxNode::parent)
479 .and_then(ast::Path::cast)
480 .and_then(|p| sema.resolve_path(&p));
481 let mut h = HlTag::Symbol(SymbolKind::SelfParam).into();
482 if self_param_is_mut
483 || matches!(self_path,
484 Some(hir::PathResolution::Local(local))
485 if local.is_self(db)
486 && (local.is_mut(db) || local.ty(db).is_mutable_reference())
487 )
488 {
489 h |= HlMod::Mutable
490 }
491
492 if let Some(hir::PathResolution::Local(local)) = self_path {
493 if is_consumed_lvalue(element, &local, db) {
494 h |= HlMod::Consuming;
495 }
496 }
497
498 h
499 }
500 T![ref] => element
501 .parent()
502 .and_then(ast::IdentPat::cast)
503 .and_then(|ident_pat| {
504 if sema.is_unsafe_ident_pat(&ident_pat) {
505 Some(HlMod::Unsafe)
506 } else {
507 None
508 }
509 })
510 .map(|modifier| h | modifier)
511 .unwrap_or(h),
512 _ => h,
513 }
514 }
515
516 _ => return None,
517 };
518
519 return Some((highlight, binding_hash));
520
521 fn calc_binding_hash(name: &Name, shadow_count: u32) -> u64 {
522 fn hash<T: std::hash::Hash + std::fmt::Debug>(x: T) -> u64 {
523 use std::{collections::hash_map::DefaultHasher, hash::Hasher};
524
525 let mut hasher = DefaultHasher::new();
526 x.hash(&mut hasher);
527 hasher.finish()
528 }
529
530 hash((name, shadow_count))
531 }
532}
533
534fn is_child_of_impl(element: &SyntaxElement) -> bool {
535 match element.parent() {
536 Some(e) => e.kind() == IMPL,
537 _ => false,
538 }
539}
540
541fn highlight_func_by_name_ref(
542 sema: &Semantics<RootDatabase>,
543 name_ref: &ast::NameRef,
544) -> Option<Highlight> {
545 let method_call = name_ref.syntax().parent().and_then(ast::MethodCallExpr::cast)?;
546 highlight_method_call(sema, &method_call)
547}
548
549fn highlight_method_call(
550 sema: &Semantics<RootDatabase>,
551 method_call: &ast::MethodCallExpr,
552) -> Option<Highlight> {
553 let func = sema.resolve_method_call(&method_call)?;
554 let mut h = HlTag::Symbol(SymbolKind::Function).into();
555 h |= HlMod::Associated;
556 if func.is_unsafe(sema.db) || sema.is_unsafe_method_call(&method_call) {
557 h |= HlMod::Unsafe;
558 }
559 if let Some(self_param) = func.self_param(sema.db) {
560 match self_param.access(sema.db) {
561 hir::Access::Shared => (),
562 hir::Access::Exclusive => h |= HlMod::Mutable,
563 hir::Access::Owned => {
564 if let Some(receiver_ty) =
565 method_call.receiver().and_then(|it| sema.type_of_expr(&it))
566 {
567 if !receiver_ty.is_copy(sema.db) {
568 h |= HlMod::Consuming
569 }
570 }
571 }
572 }
573 }
574 Some(h)
575}
576
577fn highlight_def(db: &RootDatabase, def: Definition) -> Highlight {
578 match def {
579 Definition::Macro(_) => HlTag::Symbol(SymbolKind::Macro),
580 Definition::Field(_) => HlTag::Symbol(SymbolKind::Field),
581 Definition::ModuleDef(def) => match def {
582 hir::ModuleDef::Module(_) => HlTag::Symbol(SymbolKind::Module),
583 hir::ModuleDef::Function(func) => {
584 let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Function));
585 if func.as_assoc_item(db).is_some() {
586 h |= HlMod::Associated;
587 if func.self_param(db).is_none() {
588 h |= HlMod::Static
589 }
590 }
591 if func.is_unsafe(db) {
592 h |= HlMod::Unsafe;
593 }
594 return h;
595 }
596 hir::ModuleDef::Adt(hir::Adt::Struct(_)) => HlTag::Symbol(SymbolKind::Struct),
597 hir::ModuleDef::Adt(hir::Adt::Enum(_)) => HlTag::Symbol(SymbolKind::Enum),
598 hir::ModuleDef::Adt(hir::Adt::Union(_)) => HlTag::Symbol(SymbolKind::Union),
599 hir::ModuleDef::Variant(_) => HlTag::Symbol(SymbolKind::Variant),
600 hir::ModuleDef::Const(konst) => {
601 let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Const));
602 if konst.as_assoc_item(db).is_some() {
603 h |= HlMod::Associated
604 }
605 return h;
606 }
607 hir::ModuleDef::Trait(_) => HlTag::Symbol(SymbolKind::Trait),
608 hir::ModuleDef::TypeAlias(type_) => {
609 let mut h = Highlight::new(HlTag::Symbol(SymbolKind::TypeAlias));
610 if type_.as_assoc_item(db).is_some() {
611 h |= HlMod::Associated
612 }
613 return h;
614 }
615 hir::ModuleDef::BuiltinType(_) => HlTag::BuiltinType,
616 hir::ModuleDef::Static(s) => {
617 let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Static));
618 if s.is_mut(db) {
619 h |= HlMod::Mutable;
620 h |= HlMod::Unsafe;
621 }
622 return h;
623 }
624 },
625 Definition::SelfType(_) => HlTag::Symbol(SymbolKind::Impl),
626 Definition::TypeParam(_) => HlTag::Symbol(SymbolKind::TypeParam),
627 Definition::ConstParam(_) => HlTag::Symbol(SymbolKind::ConstParam),
628 Definition::Local(local) => {
629 let tag = if local.is_param(db) {
630 HlTag::Symbol(SymbolKind::ValueParam)
631 } else {
632 HlTag::Symbol(SymbolKind::Local)
633 };
634 let mut h = Highlight::new(tag);
635 if local.is_mut(db) || local.ty(db).is_mutable_reference() {
636 h |= HlMod::Mutable;
637 }
638 if local.ty(db).as_callable(db).is_some() || local.ty(db).impls_fnonce(db) {
639 h |= HlMod::Callable;
640 }
641 return h;
642 }
643 Definition::LifetimeParam(_) => HlTag::Symbol(SymbolKind::LifetimeParam),
644 Definition::Label(_) => HlTag::Symbol(SymbolKind::Label),
645 }
646 .into()
647}
648
649fn highlight_name_by_syntax(name: ast::Name) -> Highlight {
650 let default = HlTag::UnresolvedReference;
651
652 let parent = match name.syntax().parent() {
653 Some(it) => it,
654 _ => return default.into(),
655 };
656
657 let tag = match parent.kind() {
658 STRUCT => HlTag::Symbol(SymbolKind::Struct),
659 ENUM => HlTag::Symbol(SymbolKind::Enum),
660 VARIANT => HlTag::Symbol(SymbolKind::Variant),
661 UNION => HlTag::Symbol(SymbolKind::Union),
662 TRAIT => HlTag::Symbol(SymbolKind::Trait),
663 TYPE_ALIAS => HlTag::Symbol(SymbolKind::TypeAlias),
664 TYPE_PARAM => HlTag::Symbol(SymbolKind::TypeParam),
665 RECORD_FIELD => HlTag::Symbol(SymbolKind::Field),
666 MODULE => HlTag::Symbol(SymbolKind::Module),
667 FN => HlTag::Symbol(SymbolKind::Function),
668 CONST => HlTag::Symbol(SymbolKind::Const),
669 STATIC => HlTag::Symbol(SymbolKind::Static),
670 IDENT_PAT => HlTag::Symbol(SymbolKind::Local),
671 _ => default,
672 };
673
674 tag.into()
675}
676
677fn highlight_name_ref_by_syntax(name: ast::NameRef, sema: &Semantics<RootDatabase>) -> Highlight {
678 let default = HlTag::UnresolvedReference;
679
680 let parent = match name.syntax().parent() {
681 Some(it) => it,
682 _ => return default.into(),
683 };
684
685 match parent.kind() {
686 METHOD_CALL_EXPR => {
687 return ast::MethodCallExpr::cast(parent)
688 .and_then(|method_call| highlight_method_call(sema, &method_call))
689 .unwrap_or_else(|| HlTag::Symbol(SymbolKind::Function).into());
690 }
691 FIELD_EXPR => {
692 let h = HlTag::Symbol(SymbolKind::Field);
693 let is_union = ast::FieldExpr::cast(parent)
694 .and_then(|field_expr| {
695 let field = sema.resolve_field(&field_expr)?;
696 Some(if let VariantDef::Union(_) = field.parent_def(sema.db) {
697 true
698 } else {
699 false
700 })
701 })
702 .unwrap_or(false);
703 if is_union {
704 h | HlMod::Unsafe
705 } else {
706 h.into()
707 }
708 }
709 PATH_SEGMENT => {
710 let path = match parent.parent().and_then(ast::Path::cast) {
711 Some(it) => it,
712 _ => return default.into(),
713 };
714 let expr = match path.syntax().parent().and_then(ast::PathExpr::cast) {
715 Some(it) => it,
716 _ => {
717 // within path, decide whether it is module or adt by checking for uppercase name
718 return if name.text().chars().next().unwrap_or_default().is_uppercase() {
719 HlTag::Symbol(SymbolKind::Struct)
720 } else {
721 HlTag::Symbol(SymbolKind::Module)
722 }
723 .into();
724 }
725 };
726 let parent = match expr.syntax().parent() {
727 Some(it) => it,
728 None => return default.into(),
729 };
730
731 match parent.kind() {
732 CALL_EXPR => HlTag::Symbol(SymbolKind::Function).into(),
733 _ => if name.text().chars().next().unwrap_or_default().is_uppercase() {
734 HlTag::Symbol(SymbolKind::Struct)
735 } else {
736 HlTag::Symbol(SymbolKind::Const)
737 }
738 .into(),
739 }
740 }
741 _ => default.into(),
742 }
743}
diff --git a/crates/ide/src/syntax_highlighting/format.rs b/crates/ide/src/syntax_highlighting/format.rs
index d807ad0ad..a74ca844b 100644
--- a/crates/ide/src/syntax_highlighting/format.rs
+++ b/crates/ide/src/syntax_highlighting/format.rs
@@ -1,60 +1,48 @@
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::{HlRange, HlTag, SymbolKind}; 7use crate::{syntax_highlighting::highlights::Highlights, HlRange, HlTag, SymbolKind};
8 8
9use super::highlights::Highlights; 9pub(super) fn highlight_format_string(
10 stack: &mut Highlights,
11 string: &ast::String,
12 range: TextRange,
13) {
14 if is_format_string(string).is_none() {
15 return;
16 }
10 17
11#[derive(Default)] 18 string.lex_format_specifier(|piece_range, kind| {
12pub(super) struct FormatStringHighlighter { 19 if let Some(highlight) = highlight_format_specifier(kind) {
13 format_string: Option<SyntaxElement>, 20 stack.add(HlRange {
21 range: piece_range + range.start(),
22 highlight: highlight.into(),
23 binding_hash: None,
24 });
25 }
26 });
14} 27}
15 28
16impl FormatStringHighlighter { 29fn is_format_string(string: &ast::String) -> Option<()> {
17 pub(super) fn check_for_format_string(&mut self, parent: &SyntaxNode) { 30 let parent = string.syntax().parent();
18 // Check if macro takes a format string and remember it for highlighting later. 31
19 // The macros that accept a format string expand to a compiler builtin macros 32 let name = parent.parent().and_then(ast::MacroCall::cast)?.path()?.segment()?.name_ref()?;
20 // `format_args` and `format_args_nl`. 33 if !matches!(name.text().as_str(), "format_args" | "format_args_nl") {
21 if let Some(name) = parent 34 return None;
22 .parent()
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 } 35 }
40 pub(super) fn highlight_format_string( 36
41 &self, 37 let first_literal = parent
42 stack: &mut Highlights, 38 .children_with_tokens()
43 string: &impl HasFormatSpecifier, 39 .filter_map(|it| it.as_token().cloned().and_then(ast::String::cast))
44 range: TextRange, 40 .next()?;
45 ) { 41 if &first_literal != string {
46 if self.format_string.as_ref() == Some(&SyntaxElement::from(string.syntax().clone())) { 42 return None;
47 string.lex_format_specifier(|piece_range, kind| {
48 if let Some(highlight) = highlight_format_specifier(kind) {
49 stack.add(HlRange {
50 range: piece_range + range.start(),
51 highlight: highlight.into(),
52 binding_hash: None,
53 });
54 }
55 });
56 }
57 } 43 }
44
45 Some(())
58} 46}
59 47
60fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HlTag> { 48fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HlTag> {
@@ -70,7 +58,9 @@ fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HlTag> {
70 | FormatSpecifier::Dot 58 | FormatSpecifier::Dot
71 | FormatSpecifier::Asterisk 59 | FormatSpecifier::Asterisk
72 | FormatSpecifier::QuestionMark => HlTag::FormatSpecifier, 60 | FormatSpecifier::QuestionMark => HlTag::FormatSpecifier,
61
73 FormatSpecifier::Integer | FormatSpecifier::Zero => HlTag::NumericLiteral, 62 FormatSpecifier::Integer | FormatSpecifier::Zero => HlTag::NumericLiteral,
63
74 FormatSpecifier::Identifier => HlTag::Symbol(SymbolKind::Local), 64 FormatSpecifier::Identifier => HlTag::Symbol(SymbolKind::Local),
75 }) 65 })
76} 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
index 11c11ed28..c6f0417ec 100644
--- a/crates/ide/src/syntax_highlighting/highlights.rs
+++ b/crates/ide/src/syntax_highlighting/highlights.rs
@@ -51,18 +51,20 @@ impl Node {
51 } 51 }
52 } 52 }
53 53
54 let (start, len) = 54 let overlapping =
55 equal_range_by(&self.nested, |n| ordering(n.hl_range.range, hl_range.range)); 55 equal_range_by(&self.nested, |n| ordering(n.hl_range.range, hl_range.range));
56 56
57 if len == 1 && self.nested[start].hl_range.range.contains_range(hl_range.range) { 57 if overlapping.len() == 1
58 return self.nested[start].add(hl_range); 58 && self.nested[overlapping.start].hl_range.range.contains_range(hl_range.range)
59 {
60 return self.nested[overlapping.start].add(hl_range);
59 } 61 }
60 62
61 let nested = self 63 let nested = self
62 .nested 64 .nested
63 .splice(start..start + len, iter::once(Node::new(hl_range))) 65 .splice(overlapping.clone(), iter::once(Node::new(hl_range)))
64 .collect::<Vec<_>>(); 66 .collect::<Vec<_>>();
65 self.nested[start].nested = nested; 67 self.nested[overlapping.start].nested = nested;
66 } 68 }
67 69
68 fn flatten(&self, acc: &mut Vec<HlRange>) { 70 fn flatten(&self, acc: &mut Vec<HlRange>) {
diff --git a/crates/ide/src/syntax_highlighting/injection.rs b/crates/ide/src/syntax_highlighting/inject.rs
index 008d5ce24..281461493 100644
--- a/crates/ide/src/syntax_highlighting/injection.rs
+++ b/crates/ide/src/syntax_highlighting/inject.rs
@@ -1,17 +1,14 @@
1//! Syntax highlighting injections such as highlighting of documentation tests. 1//! "Recursive" Syntax highlighting for code in doctests and fixtures.
2
3use std::convert::TryFrom;
4 2
5use hir::Semantics; 3use hir::Semantics;
6use ide_db::call_info::ActiveParameter; 4use ide_db::call_info::ActiveParameter;
7use itertools::Itertools;
8use syntax::{ast, AstToken, SyntaxNode, SyntaxToken, TextRange, TextSize}; 5use syntax::{ast, AstToken, SyntaxNode, SyntaxToken, TextRange, TextSize};
9 6
10use crate::{Analysis, HlMod, HlRange, HlTag, RootDatabase}; 7use crate::{Analysis, HlMod, HlRange, HlTag, RootDatabase};
11 8
12use super::{highlights::Highlights, injector::Injector}; 9use super::{highlights::Highlights, injector::Injector};
13 10
14pub(super) fn highlight_injection( 11pub(super) fn ra_fixture(
15 hl: &mut Highlights, 12 hl: &mut Highlights,
16 sema: &Semantics<RootDatabase>, 13 sema: &Semantics<RootDatabase>,
17 literal: ast::String, 14 literal: ast::String,
@@ -84,107 +81,78 @@ const RUSTDOC_FENCE_TOKENS: &[&'static str] = &[
84 "edition2021", 81 "edition2021",
85]; 82];
86 83
87/// Extracts Rust code from documentation comments as well as a mapping from 84/// Injection of syntax highlighting of doctests.
88/// the extracted source code back to the original source ranges. 85pub(super) fn doc_comment(hl: &mut Highlights, node: &SyntaxNode) {
89/// Lastly, a vector of new comment highlight ranges (spanning only the 86 let doc_comments = node
90/// comment prefix) is returned which is used in the syntax highlighting 87 .children_with_tokens()
91/// injection to replace the previous (line-spanning) comment ranges. 88 .filter_map(|it| it.into_token().and_then(ast::Comment::cast))
92pub(super) fn extract_doc_comments(node: &SyntaxNode) -> Option<(Vec<HlRange>, Injector)> { 89 .filter(|it| it.kind().doc.is_some());
90
91 if !doc_comments.clone().any(|it| it.text().contains(RUSTDOC_FENCE)) {
92 return;
93 }
94
93 let mut inj = Injector::default(); 95 let mut inj = Injector::default();
94 // wrap the doctest into function body to get correct syntax highlighting 96 inj.add_unmapped("fn doctest() {\n");
95 let prefix = "fn doctest() {\n";
96 let suffix = "}\n";
97 97
98 let mut line_start = TextSize::of(prefix);
99 let mut is_codeblock = false; 98 let mut is_codeblock = false;
100 let mut is_doctest = false; 99 let mut is_doctest = false;
100
101 // Replace the original, line-spanning comment ranges by new, only comment-prefix 101 // Replace the original, line-spanning comment ranges by new, only comment-prefix
102 // spanning comment ranges. 102 // spanning comment ranges.
103 let mut new_comments = Vec::new(); 103 let mut new_comments = Vec::new();
104 104 for comment in doc_comments {
105 inj.add_unmapped(prefix); 105 match comment.text().find(RUSTDOC_FENCE) {
106 let doctest = node 106 Some(idx) => {
107 .children_with_tokens()
108 .filter_map(|el| el.into_token().and_then(ast::Comment::cast))
109 .filter(|comment| comment.kind().doc.is_some())
110 .filter(|comment| {
111 if let Some(idx) = comment.text().find(RUSTDOC_FENCE) {
112 is_codeblock = !is_codeblock; 107 is_codeblock = !is_codeblock;
113 // Check whether code is rust by inspecting fence guards 108 // Check whether code is rust by inspecting fence guards
114 let guards = &comment.text()[idx + RUSTDOC_FENCE.len()..]; 109 let guards = &comment.text()[idx + RUSTDOC_FENCE.len()..];
115 let is_rust = 110 let is_rust =
116 guards.split(',').all(|sub| RUSTDOC_FENCE_TOKENS.contains(&sub.trim())); 111 guards.split(',').all(|sub| RUSTDOC_FENCE_TOKENS.contains(&sub.trim()));
117 is_doctest = is_codeblock && is_rust; 112 is_doctest = is_codeblock && is_rust;
118 false 113 continue;
119 } else {
120 is_doctest
121 } 114 }
122 }) 115 None if !is_doctest => continue,
123 .map(|comment| { 116 None => (),
124 let prefix_len = comment.prefix().len(); 117 }
125 let line: &str = comment.text().as_str();
126 let range = comment.syntax().text_range();
127
128 // whitespace after comment is ignored
129 let pos = if let Some(ws) = line.chars().nth(prefix_len).filter(|c| c.is_whitespace()) {
130 prefix_len + ws.len_utf8()
131 } else {
132 prefix_len
133 };
134
135 // lines marked with `#` should be ignored in output, we skip the `#` char
136 let pos = if let Some(ws) = line.chars().nth(pos).filter(|&c| c == '#') {
137 pos + ws.len_utf8()
138 } else {
139 pos
140 };
141
142 new_comments.push(HlRange {
143 range: TextRange::new(
144 range.start(),
145 range.start() + TextSize::try_from(pos).unwrap(),
146 ),
147 highlight: HlTag::Comment | HlMod::Documentation,
148 binding_hash: None,
149 });
150 line_start += range.len() - TextSize::try_from(pos).unwrap();
151 line_start += TextSize::of("\n");
152
153 inj.add(
154 &line[pos..],
155 TextRange::new(range.start() + TextSize::try_from(pos).unwrap(), range.end()),
156 );
157 inj.add_unmapped("\n");
158 line[pos..].to_owned()
159 })
160 .join("\n");
161 inj.add_unmapped(suffix);
162
163 if doctest.is_empty() {
164 return None;
165 }
166 118
167 Some((new_comments, inj)) 119 let line: &str = comment.text().as_str();
168} 120 let range = comment.syntax().text_range();
169 121
170/// Injection of syntax highlighting of doctests. 122 let mut pos = TextSize::of(comment.prefix());
171pub(super) fn highlight_doc_comment( 123 // whitespace after comment is ignored
172 new_comments: Vec<HlRange>, 124 if let Some(ws) = line[pos.into()..].chars().next().filter(|c| c.is_whitespace()) {
173 inj: Injector, 125 pos += TextSize::of(ws);
174 stack: &mut Highlights, 126 }
175) { 127 // lines marked with `#` should be ignored in output, we skip the `#` char
176 let (analysis, tmp_file_id) = Analysis::from_single_file(inj.text().to_string()); 128 if let Some(ws) = line[pos.into()..].chars().next().filter(|&c| c == '#') {
177 for comment in new_comments { 129 pos += TextSize::of(ws);
178 stack.add(comment); 130 }
131
132 new_comments.push(TextRange::at(range.start(), pos));
133
134 inj.add(&line[pos.into()..], TextRange::new(range.start() + pos, range.end()));
135 inj.add_unmapped("\n");
179 } 136 }
137 inj.add_unmapped("\n}");
138
139 let (analysis, tmp_file_id) = Analysis::from_single_file(inj.text().to_string());
180 140
181 for h in analysis.with_db(|db| super::highlight(db, tmp_file_id, None, true)).unwrap() { 141 for h in analysis.with_db(|db| super::highlight(db, tmp_file_id, None, true)).unwrap() {
182 for r in inj.map_range_up(h.range) { 142 for r in inj.map_range_up(h.range) {
183 stack.add(HlRange { 143 hl.add(HlRange {
184 range: r, 144 range: r,
185 highlight: h.highlight | HlMod::Injected, 145 highlight: h.highlight | HlMod::Injected,
186 binding_hash: h.binding_hash, 146 binding_hash: h.binding_hash,
187 }); 147 });
188 } 148 }
189 } 149 }
150
151 for range in new_comments {
152 hl.add(HlRange {
153 range,
154 highlight: HlTag::Comment | HlMod::Documentation,
155 binding_hash: None,
156 });
157 }
190} 158}
diff --git a/crates/ide/src/syntax_highlighting/injector.rs b/crates/ide/src/syntax_highlighting/injector.rs
index e8f17eb69..fd4025694 100644
--- a/crates/ide/src/syntax_highlighting/injector.rs
+++ b/crates/ide/src/syntax_highlighting/injector.rs
@@ -33,8 +33,7 @@ impl Injector {
33 &self.buf 33 &self.buf
34 } 34 }
35 pub(super) fn map_range_up(&self, range: TextRange) -> impl Iterator<Item = TextRange> + '_ { 35 pub(super) fn map_range_up(&self, range: TextRange) -> impl Iterator<Item = TextRange> + '_ {
36 let (start, len) = equal_range_by(&self.ranges, |&(r, _)| ordering(r, range)); 36 equal_range_by(&self.ranges, |&(r, _)| ordering(r, range)).filter_map(move |i| {
37 (start..start + len).filter_map(move |i| {
38 let (target_range, delta) = self.ranges[i]; 37 let (target_range, delta) = self.ranges[i];
39 let intersection = target_range.intersect(range).unwrap(); 38 let intersection = target_range.intersect(range).unwrap();
40 Some(intersection + delta?) 39 Some(intersection + delta?)
diff --git a/crates/ide/src/syntax_highlighting/macro_rules.rs b/crates/ide/src/syntax_highlighting/macro_rules.rs
index 21d8a9835..44620e912 100644
--- a/crates/ide/src/syntax_highlighting/macro_rules.rs
+++ b/crates/ide/src/syntax_highlighting/macro_rules.rs
@@ -119,7 +119,7 @@ fn is_metavariable(element: SyntaxElement) -> Option<TextRange> {
119 let tok = element.as_token()?; 119 let tok = element.as_token()?;
120 match tok.kind() { 120 match tok.kind() {
121 kind if kind == SyntaxKind::IDENT || kind.is_keyword() => { 121 kind if kind == SyntaxKind::IDENT || kind.is_keyword() => {
122 if let Some(_dollar) = tok.prev_token().filter(|t| t.kind() == SyntaxKind::DOLLAR) { 122 if let Some(_dollar) = tok.prev_token().filter(|t| t.kind() == T![$]) {
123 return Some(tok.text_range()); 123 return Some(tok.text_range());
124 } 124 }
125 } 125 }
diff --git a/crates/ide/src/syntax_highlighting/tags.rs b/crates/ide/src/syntax_highlighting/tags.rs
index 2f39bcc8e..8dd05ac52 100644
--- a/crates/ide/src/syntax_highlighting/tags.rs
+++ b/crates/ide/src/syntax_highlighting/tags.rs
@@ -29,7 +29,7 @@ pub enum HlTag {
29 EscapeSequence, 29 EscapeSequence,
30 FormatSpecifier, 30 FormatSpecifier,
31 Keyword, 31 Keyword,
32 Punctuation, 32 Punctuation(HlPunct),
33 Operator, 33 Operator,
34 UnresolvedReference, 34 UnresolvedReference,
35 35
@@ -61,6 +61,28 @@ pub enum HlMod {
61 Unsafe, 61 Unsafe,
62} 62}
63 63
64#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
65pub enum HlPunct {
66 /// []
67 Bracket,
68 /// {}
69 Brace,
70 /// ()
71 Parenthesis,
72 /// <>
73 Angle,
74 /// ,
75 Comma,
76 /// .
77 Dot,
78 /// :
79 Colon,
80 /// ;
81 Semi,
82 ///
83 Other,
84}
85
64impl HlTag { 86impl HlTag {
65 fn as_str(self) -> &'static str { 87 fn as_str(self) -> &'static str {
66 match self { 88 match self {
@@ -95,7 +117,17 @@ impl HlTag {
95 HlTag::EscapeSequence => "escape_sequence", 117 HlTag::EscapeSequence => "escape_sequence",
96 HlTag::FormatSpecifier => "format_specifier", 118 HlTag::FormatSpecifier => "format_specifier",
97 HlTag::Keyword => "keyword", 119 HlTag::Keyword => "keyword",
98 HlTag::Punctuation => "punctuation", 120 HlTag::Punctuation(punct) => match punct {
121 HlPunct::Bracket => "bracket",
122 HlPunct::Brace => "brace",
123 HlPunct::Parenthesis => "parenthesis",
124 HlPunct::Angle => "angle",
125 HlPunct::Comma => "comma",
126 HlPunct::Dot => "dot",
127 HlPunct::Colon => "colon",
128 HlPunct::Semi => "semicolon",
129 HlPunct::Other => "punctuation",
130 },
99 HlTag::NumericLiteral => "numeric_literal", 131 HlTag::NumericLiteral => "numeric_literal",
100 HlTag::Operator => "operator", 132 HlTag::Operator => "operator",
101 HlTag::StringLiteral => "string_literal", 133 HlTag::StringLiteral => "string_literal",
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html b/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html
index 506ebe60e..e36e6fc3f 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html
@@ -36,22 +36,22 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
36 36
37.unresolved_reference { color: #FC5555; text-decoration: wavy underline; } 37.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
38</style> 38</style>
39<pre><code><span class="keyword">fn</span> <span class="function declaration">not_static</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span><span class="punctuation">}</span> 39<pre><code><span class="keyword">fn</span> <span class="function declaration">not_static</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
40 40
41<span class="keyword">struct</span> <span class="struct declaration">foo</span> <span class="punctuation">{</span><span class="punctuation">}</span> 41<span class="keyword">struct</span> <span class="struct declaration">foo</span> <span class="brace">{</span><span class="brace">}</span>
42 42
43<span class="keyword">impl</span> <span class="struct">foo</span> <span class="punctuation">{</span> 43<span class="keyword">impl</span> <span class="struct">foo</span> <span class="brace">{</span>
44 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration static associated">is_static</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span><span class="punctuation">}</span> 44 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration static associated">is_static</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
45 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration associated">is_not_static</span><span class="punctuation">(</span><span class="operator">&</span><span class="self_keyword">self</span><span class="punctuation">)</span> <span class="punctuation">{</span><span class="punctuation">}</span> 45 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration associated">is_not_static</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
46<span class="punctuation">}</span> 46<span class="brace">}</span>
47 47
48<span class="keyword">trait</span> <span class="trait declaration">t</span> <span class="punctuation">{</span> 48<span class="keyword">trait</span> <span class="trait declaration">t</span> <span class="brace">{</span>
49 <span class="keyword">fn</span> <span class="function declaration static associated">t_is_static</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span><span class="punctuation">}</span> 49 <span class="keyword">fn</span> <span class="function declaration static associated">t_is_static</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
50 <span class="keyword">fn</span> <span class="function declaration associated">t_is_not_static</span><span class="punctuation">(</span><span class="operator">&</span><span class="self_keyword">self</span><span class="punctuation">)</span> <span class="punctuation">{</span><span class="punctuation">}</span> 50 <span class="keyword">fn</span> <span class="function declaration associated">t_is_not_static</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
51<span class="punctuation">}</span> 51<span class="brace">}</span>
52 52
53<span class="keyword">impl</span> <span class="trait">t</span> <span class="keyword">for</span> <span class="struct">foo</span> <span class="punctuation">{</span> 53<span class="keyword">impl</span> <span class="trait">t</span> <span class="keyword">for</span> <span class="struct">foo</span> <span class="brace">{</span>
54 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration static associated">is_static</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span><span class="punctuation">}</span> 54 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration static associated">is_static</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
55 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration associated">is_not_static</span><span class="punctuation">(</span><span class="operator">&</span><span class="self_keyword">self</span><span class="punctuation">)</span> <span class="punctuation">{</span><span class="punctuation">}</span> 55 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration associated">is_not_static</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
56<span class="punctuation">}</span> 56<span class="brace">}</span>
57 </code></pre> \ No newline at end of file 57 </code></pre> \ No newline at end of file
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
index 7d1d2a839..6dadda1c1 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
@@ -37,72 +37,72 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
37.unresolved_reference { color: #FC5555; text-decoration: wavy underline; } 37.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
38</style> 38</style>
39<pre><code><span class="comment documentation">/// ```</span> 39<pre><code><span class="comment documentation">/// ```</span>
40<span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="punctuation injected">_</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="string_literal injected">"early doctests should not go boom"</span><span class="punctuation injected">;</span> 40<span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="punctuation injected">_</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="string_literal injected">"early doctests should not go boom"</span><span class="semicolon injected">;</span>
41<span class="comment documentation">/// ```</span> 41<span class="comment documentation">/// ```</span>
42<span class="keyword">struct</span> <span class="struct declaration">Foo</span> <span class="punctuation">{</span> 42<span class="keyword">struct</span> <span class="struct declaration">Foo</span> <span class="brace">{</span>
43 <span class="field declaration">bar</span><span class="punctuation">:</span> <span class="builtin_type">bool</span><span class="punctuation">,</span> 43 <span class="field declaration">bar</span><span class="colon">:</span> <span class="builtin_type">bool</span><span class="comma">,</span>
44<span class="punctuation">}</span> 44<span class="brace">}</span>
45 45
46<span class="keyword">impl</span> <span class="struct">Foo</span> <span class="punctuation">{</span> 46<span class="keyword">impl</span> <span class="struct">Foo</span> <span class="brace">{</span>
47 <span class="comment documentation">/// ```</span> 47 <span class="comment documentation">/// ```</span>
48 <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="punctuation injected">_</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="string_literal injected">"Call me</span> 48 <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="punctuation injected">_</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="string_literal injected">"Call me</span>
49 <span class="comment">// KILLER WHALE</span> 49 <span class="comment">// KILLER WHALE</span>
50 <span class="comment documentation">/// </span><span class="string_literal injected"> Ishmael."</span><span class="punctuation injected">;</span> 50 <span class="comment documentation">/// </span><span class="string_literal injected"> Ishmael."</span><span class="semicolon injected">;</span>
51 <span class="comment documentation">/// ```</span> 51 <span class="comment documentation">/// ```</span>
52 <span class="keyword">pub</span> <span class="keyword">const</span> <span class="constant declaration associated">bar</span><span class="punctuation">:</span> <span class="builtin_type">bool</span> <span class="operator">=</span> <span class="bool_literal">true</span><span class="punctuation">;</span> 52 <span class="keyword">pub</span> <span class="keyword">const</span> <span class="constant declaration associated">bar</span><span class="colon">:</span> <span class="builtin_type">bool</span> <span class="operator">=</span> <span class="bool_literal">true</span><span class="semicolon">;</span>
53 53
54 <span class="comment documentation">/// Constructs a new `Foo`.</span> 54 <span class="comment documentation">/// Constructs a new `Foo`.</span>
55 <span class="comment documentation">///</span> 55 <span class="comment documentation">///</span>
56 <span class="comment documentation">/// # Examples</span> 56 <span class="comment documentation">/// # Examples</span>
57 <span class="comment documentation">///</span> 57 <span class="comment documentation">///</span>
58 <span class="comment documentation">/// ```</span> 58 <span class="comment documentation">/// ```</span>
59 <span class="comment documentation">/// #</span><span class="none injected"> </span><span class="attribute attribute injected">#</span><span class="attribute attribute injected">!</span><span class="attribute attribute injected">[</span><span class="function attribute injected">allow</span><span class="punctuation attribute injected">(</span><span class="attribute attribute injected">unused_mut</span><span class="punctuation attribute injected">)</span><span class="attribute attribute injected">]</span> 59 <span class="comment documentation">/// #</span><span class="none injected"> </span><span class="attribute attribute injected">#</span><span class="attribute attribute injected">!</span><span class="attribute attribute injected">[</span><span class="function attribute injected">allow</span><span class="parenthesis attribute injected">(</span><span class="attribute attribute injected">unused_mut</span><span class="parenthesis attribute injected">)</span><span class="attribute attribute injected">]</span>
60 <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="keyword injected">mut</span><span class="none injected"> </span><span class="variable declaration injected mutable">foo</span><span class="punctuation injected">:</span><span class="none injected"> </span><span class="struct injected">Foo</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="function injected">new</span><span class="punctuation injected">(</span><span class="punctuation injected">)</span><span class="punctuation injected">;</span> 60 <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="keyword injected">mut</span><span class="none injected"> </span><span class="variable declaration injected mutable">foo</span><span class="colon injected">:</span><span class="none injected"> </span><span class="struct injected">Foo</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="function injected">new</span><span class="parenthesis injected">(</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span>
61 <span class="comment documentation">/// ```</span> 61 <span class="comment documentation">/// ```</span>
62 <span class="keyword">pub</span> <span class="keyword">const</span> <span class="keyword">fn</span> <span class="function declaration static associated">new</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="operator">-&gt;</span> <span class="struct">Foo</span> <span class="punctuation">{</span> 62 <span class="keyword">pub</span> <span class="keyword">const</span> <span class="keyword">fn</span> <span class="function declaration static associated">new</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="struct">Foo</span> <span class="brace">{</span>
63 <span class="struct">Foo</span> <span class="punctuation">{</span> <span class="field">bar</span><span class="punctuation">:</span> <span class="bool_literal">true</span> <span class="punctuation">}</span> 63 <span class="struct">Foo</span> <span class="brace">{</span> <span class="field">bar</span><span class="colon">:</span> <span class="bool_literal">true</span> <span class="brace">}</span>
64 <span class="punctuation">}</span> 64 <span class="brace">}</span>
65 65
66 <span class="comment documentation">/// `bar` method on `Foo`.</span> 66 <span class="comment documentation">/// `bar` method on `Foo`.</span>
67 <span class="comment documentation">///</span> 67 <span class="comment documentation">///</span>
68 <span class="comment documentation">/// # Examples</span> 68 <span class="comment documentation">/// # Examples</span>
69 <span class="comment documentation">///</span> 69 <span class="comment documentation">///</span>
70 <span class="comment documentation">/// ```</span> 70 <span class="comment documentation">/// ```</span>
71 <span class="comment documentation">/// </span><span class="keyword injected">use</span><span class="none injected"> </span><span class="module injected">x</span><span class="operator injected">::</span><span class="module injected">y</span><span class="punctuation injected">;</span> 71 <span class="comment documentation">/// </span><span class="keyword injected">use</span><span class="none injected"> </span><span class="module injected">x</span><span class="operator injected">::</span><span class="module injected">y</span><span class="semicolon injected">;</span>
72 <span class="comment documentation">///</span> 72 <span class="comment documentation">///</span>
73 <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="variable declaration injected">foo</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="function injected">new</span><span class="punctuation injected">(</span><span class="punctuation injected">)</span><span class="punctuation injected">;</span> 73 <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="variable declaration injected">foo</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="function injected">new</span><span class="parenthesis injected">(</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span>
74 <span class="comment documentation">///</span> 74 <span class="comment documentation">///</span>
75 <span class="comment documentation">/// </span><span class="comment injected">// calls bar on foo</span> 75 <span class="comment documentation">/// </span><span class="comment injected">// calls bar on foo</span>
76 <span class="comment documentation">/// </span><span class="macro injected">assert!</span><span class="punctuation injected">(</span><span class="none injected">foo</span><span class="operator injected">.</span><span class="none injected">bar</span><span class="punctuation injected">(</span><span class="punctuation injected">)</span><span class="punctuation injected">)</span><span class="punctuation injected">;</span> 76 <span class="comment documentation">/// </span><span class="macro injected">assert!</span><span class="parenthesis injected">(</span><span class="none injected">foo</span><span class="operator injected">.</span><span class="none injected">bar</span><span class="parenthesis injected">(</span><span class="parenthesis injected">)</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span>
77 <span class="comment documentation">///</span> 77 <span class="comment documentation">///</span>
78 <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="variable declaration injected">bar</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="variable injected">foo</span><span class="operator injected">.</span><span class="field injected">bar</span><span class="none injected"> </span><span class="operator injected">||</span><span class="none injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="constant injected">bar</span><span class="punctuation injected">;</span> 78 <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="variable declaration injected">bar</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="variable injected">foo</span><span class="operator injected">.</span><span class="field injected">bar</span><span class="none injected"> </span><span class="operator injected">||</span><span class="none injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="constant injected">bar</span><span class="semicolon injected">;</span>
79 <span class="comment documentation">///</span> 79 <span class="comment documentation">///</span>
80 <span class="comment documentation">/// </span><span class="comment injected">/* multi-line</span> 80 <span class="comment documentation">/// </span><span class="comment injected">/* multi-line</span>
81 <span class="comment documentation">/// </span><span class="comment injected"> comment */</span> 81 <span class="comment documentation">/// </span><span class="comment injected"> comment */</span>
82 <span class="comment documentation">///</span> 82 <span class="comment documentation">///</span>
83 <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="variable declaration injected">multi_line_string</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="string_literal injected">"Foo</span> 83 <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="variable declaration injected">multi_line_string</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="string_literal injected">"Foo</span>
84 <span class="comment documentation">/// </span><span class="string_literal injected"> bar</span> 84 <span class="comment documentation">/// </span><span class="string_literal injected"> bar</span>
85 <span class="comment documentation">/// </span><span class="string_literal injected"> "</span><span class="punctuation injected">;</span> 85 <span class="comment documentation">/// </span><span class="string_literal injected"> "</span><span class="semicolon injected">;</span>
86 <span class="comment documentation">///</span> 86 <span class="comment documentation">///</span>
87 <span class="comment documentation">/// ```</span> 87 <span class="comment documentation">/// ```</span>
88 <span class="comment documentation">///</span> 88 <span class="comment documentation">///</span>
89 <span class="comment documentation">/// ```rust,no_run</span> 89 <span class="comment documentation">/// ```rust,no_run</span>
90 <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="variable declaration injected">foobar</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="function injected">new</span><span class="punctuation injected">(</span><span class="punctuation injected">)</span><span class="operator injected">.</span><span class="function injected">bar</span><span class="punctuation injected">(</span><span class="punctuation injected">)</span><span class="punctuation injected">;</span> 90 <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="variable declaration injected">foobar</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="function injected">new</span><span class="parenthesis injected">(</span><span class="parenthesis injected">)</span><span class="operator injected">.</span><span class="function injected">bar</span><span class="parenthesis injected">(</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span>
91 <span class="comment documentation">/// ```</span> 91 <span class="comment documentation">/// ```</span>
92 <span class="comment documentation">///</span> 92 <span class="comment documentation">///</span>
93 <span class="comment documentation">/// ```sh</span> 93 <span class="comment documentation">/// ```sh</span>
94 <span class="comment documentation">/// echo 1</span> 94 <span class="comment documentation">/// echo 1</span>
95 <span class="comment documentation">/// ```</span> 95 <span class="comment documentation">/// ```</span>
96 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration associated">foo</span><span class="punctuation">(</span><span class="operator">&</span><span class="self_keyword">self</span><span class="punctuation">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">bool</span> <span class="punctuation">{</span> 96 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration associated">foo</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword">self</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">bool</span> <span class="brace">{</span>
97 <span class="bool_literal">true</span> 97 <span class="bool_literal">true</span>
98 <span class="punctuation">}</span> 98 <span class="brace">}</span>
99<span class="punctuation">}</span> 99<span class="brace">}</span>
100 100
101<span class="comment documentation">/// ```</span> 101<span class="comment documentation">/// ```</span>
102<span class="comment documentation">/// </span><span class="macro injected">noop!</span><span class="punctuation injected">(</span><span class="numeric_literal injected">1</span><span class="punctuation injected">)</span><span class="punctuation injected">;</span> 102<span class="comment documentation">/// </span><span class="macro injected">noop!</span><span class="parenthesis injected">(</span><span class="numeric_literal injected">1</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span>
103<span class="comment documentation">/// ```</span> 103<span class="comment documentation">/// ```</span>
104<span class="keyword">macro_rules</span><span class="punctuation">!</span> <span class="macro declaration">noop</span> <span class="punctuation">{</span> 104<span class="keyword">macro_rules</span><span class="punctuation">!</span> <span class="macro declaration">noop</span> <span class="brace">{</span>
105 <span class="punctuation">(</span><span class="punctuation">$</span>expr<span class="punctuation">:</span>expr<span class="punctuation">)</span> <span class="operator">=</span><span class="punctuation">&gt;</span> <span class="punctuation">{</span> 105 <span class="parenthesis">(</span><span class="punctuation">$</span>expr<span class="colon">:</span>expr<span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">&gt;</span> <span class="brace">{</span>
106 <span class="punctuation">$</span>expr 106 <span class="punctuation">$</span>expr
107 <span class="punctuation">}</span> 107 <span class="brace">}</span>
108<span class="punctuation">}</span></code></pre> \ No newline at end of file 108<span class="brace">}</span></code></pre> \ No newline at end of file
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html b/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html
index ed452586a..6f7a7ffff 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html
@@ -36,6 +36,6 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
36 36
37.unresolved_reference { color: #FC5555; text-decoration: wavy underline; } 37.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
38</style> 38</style>
39<pre><code><span class="keyword">extern</span> <span class="keyword">crate</span> <span class="module">std</span><span class="punctuation">;</span> 39<pre><code><span class="keyword">extern</span> <span class="keyword">crate</span> <span class="module">std</span><span class="semicolon">;</span>
40<span class="keyword">extern</span> <span class="keyword">crate</span> <span class="module">alloc</span> <span class="keyword">as</span> <span class="module">abc</span><span class="punctuation">;</span> 40<span class="keyword">extern</span> <span class="keyword">crate</span> <span class="module">alloc</span> <span class="keyword">as</span> <span class="module">abc</span><span class="semicolon">;</span>
41</code></pre> \ No newline at end of file 41</code></pre> \ No newline at end of file
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html b/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html
index 92e7dc3e4..753b535b5 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html
@@ -36,14 +36,14 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
36 36
37.unresolved_reference { color: #FC5555; text-decoration: wavy underline; } 37.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
38</style> 38</style>
39<pre><code><span class="keyword">fn</span> <span class="function declaration">fixture</span><span class="punctuation">(</span><span class="value_param declaration">ra_fixture</span><span class="punctuation">:</span> <span class="operator">&</span><span class="builtin_type">str</span><span class="punctuation">)</span> <span class="punctuation">{</span><span class="punctuation">}</span> 39<pre><code><span class="keyword">fn</span> <span class="function declaration">fixture</span><span class="parenthesis">(</span><span class="value_param declaration">ra_fixture</span><span class="colon">:</span> <span class="operator">&</span><span class="builtin_type">str</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
40 40
41<span class="keyword">fn</span> <span class="function declaration">main</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span> 41<span class="keyword">fn</span> <span class="function declaration">main</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
42 <span class="function">fixture</span><span class="punctuation">(</span><span class="string_literal">r#"</span> 42 <span class="function">fixture</span><span class="parenthesis">(</span><span class="string_literal">r#"</span>
43 <span class="keyword">trait</span> <span class="trait declaration">Foo</span> <span class="punctuation">{</span> 43 <span class="keyword">trait</span> <span class="trait declaration">Foo</span> <span class="brace">{</span>
44 <span class="keyword">fn</span> <span class="function declaration static associated">foo</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span> 44 <span class="keyword">fn</span> <span class="function declaration static associated">foo</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
45 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"2 + 2 = {}"</span><span class="punctuation">,</span> <span class="numeric_literal">4</span><span class="punctuation">)</span><span class="punctuation">;</span> 45 <span class="macro">println!</span><span class="parenthesis">(</span><span class="string_literal">"2 + 2 = {}"</span><span class="comma">,</span> <span class="numeric_literal">4</span><span class="parenthesis">)</span><span class="semicolon">;</span>
46 <span class="punctuation">}</span> 46 <span class="brace">}</span>
47 <span class="punctuation">}</span><span class="string_literal">"#</span> 47 <span class="brace">}</span><span class="string_literal">"#</span>
48 <span class="punctuation">)</span><span class="punctuation">;</span> 48 <span class="parenthesis">)</span><span class="semicolon">;</span>
49<span class="punctuation">}</span></code></pre> \ No newline at end of file 49<span class="brace">}</span></code></pre> \ No newline at end of file
diff --git a/crates/ide/src/syntax