diff options
Diffstat (limited to 'crates/ide/src')
-rw-r--r-- | crates/ide/src/completion.rs | 2 | ||||
-rw-r--r-- | crates/ide/src/diagnostics.rs | 95 | ||||
-rw-r--r-- | crates/ide/src/diagnostics/field_shorthand.rs | 206 | ||||
-rw-r--r-- | crates/ide/src/doc_links.rs | 31 | ||||
-rw-r--r-- | crates/ide/src/goto_definition.rs | 8 | ||||
-rw-r--r-- | crates/ide/src/hover.rs | 29 | ||||
-rw-r--r-- | crates/ide/src/references.rs | 6 | ||||
-rw-r--r-- | crates/ide/src/references/rename.rs | 6 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting.rs | 224 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting/format.rs | 78 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting/macro_rules.rs | 129 |
11 files changed, 501 insertions, 313 deletions
diff --git a/crates/ide/src/completion.rs b/crates/ide/src/completion.rs index 697f691b0..b0e35b2bd 100644 --- a/crates/ide/src/completion.rs +++ b/crates/ide/src/completion.rs | |||
@@ -61,6 +61,8 @@ pub use crate::completion::{ | |||
61 | // - `expr.refm` -> `&mut expr` | 61 | // - `expr.refm` -> `&mut expr` |
62 | // - `expr.not` -> `!expr` | 62 | // - `expr.not` -> `!expr` |
63 | // - `expr.dbg` -> `dbg!(expr)` | 63 | // - `expr.dbg` -> `dbg!(expr)` |
64 | // - `expr.dbgr` -> `dbg!(&expr)` | ||
65 | // - `expr.call` -> `(expr)` | ||
64 | // | 66 | // |
65 | // There also snippet completions: | 67 | // There also snippet completions: |
66 | // | 68 | // |
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index b30cdb6ed..1e5ea4617 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs | |||
@@ -5,6 +5,7 @@ | |||
5 | //! original files. So we need to map the ranges. | 5 | //! original files. So we need to map the ranges. |
6 | 6 | ||
7 | mod fixes; | 7 | mod fixes; |
8 | mod field_shorthand; | ||
8 | 9 | ||
9 | use std::cell::RefCell; | 10 | use std::cell::RefCell; |
10 | 11 | ||
@@ -80,7 +81,7 @@ pub(crate) fn diagnostics( | |||
80 | 81 | ||
81 | for node in parse.tree().syntax().descendants() { | 82 | for node in parse.tree().syntax().descendants() { |
82 | check_unnecessary_braces_in_use_statement(&mut res, file_id, &node); | 83 | check_unnecessary_braces_in_use_statement(&mut res, file_id, &node); |
83 | check_struct_shorthand_initialization(&mut res, file_id, &node); | 84 | field_shorthand::check(&mut res, file_id, &node); |
84 | } | 85 | } |
85 | let res = RefCell::new(res); | 86 | let res = RefCell::new(res); |
86 | let sink_builder = DiagnosticSinkBuilder::new() | 87 | let sink_builder = DiagnosticSinkBuilder::new() |
@@ -188,42 +189,6 @@ fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement( | |||
188 | None | 189 | None |
189 | } | 190 | } |
190 | 191 | ||
191 | fn check_struct_shorthand_initialization( | ||
192 | acc: &mut Vec<Diagnostic>, | ||
193 | file_id: FileId, | ||
194 | node: &SyntaxNode, | ||
195 | ) -> Option<()> { | ||
196 | let record_lit = ast::RecordExpr::cast(node.clone())?; | ||
197 | let record_field_list = record_lit.record_expr_field_list()?; | ||
198 | for record_field in record_field_list.fields() { | ||
199 | if let (Some(name_ref), Some(expr)) = (record_field.name_ref(), record_field.expr()) { | ||
200 | let field_name = name_ref.syntax().text().to_string(); | ||
201 | let field_expr = expr.syntax().text().to_string(); | ||
202 | let field_name_is_tup_index = name_ref.as_tuple_field().is_some(); | ||
203 | if field_name == field_expr && !field_name_is_tup_index { | ||
204 | let mut edit_builder = TextEdit::builder(); | ||
205 | edit_builder.delete(record_field.syntax().text_range()); | ||
206 | edit_builder.insert(record_field.syntax().text_range().start(), field_name); | ||
207 | let edit = edit_builder.finish(); | ||
208 | |||
209 | let field_range = record_field.syntax().text_range(); | ||
210 | acc.push(Diagnostic { | ||
211 | // name: None, | ||
212 | range: field_range, | ||
213 | message: "Shorthand struct initialization".to_string(), | ||
214 | severity: Severity::WeakWarning, | ||
215 | fix: Some(Fix::new( | ||
216 | "Use struct shorthand initialization", | ||
217 | SourceFileEdit { file_id, edit }.into(), | ||
218 | field_range, | ||
219 | )), | ||
220 | }); | ||
221 | } | ||
222 | } | ||
223 | } | ||
224 | Some(()) | ||
225 | } | ||
226 | |||
227 | #[cfg(test)] | 192 | #[cfg(test)] |
228 | mod tests { | 193 | mod tests { |
229 | use expect_test::{expect, Expect}; | 194 | use expect_test::{expect, Expect}; |
@@ -237,7 +202,7 @@ mod tests { | |||
237 | /// * a diagnostic is produced | 202 | /// * a diagnostic is produced |
238 | /// * this diagnostic fix trigger range touches the input cursor position | 203 | /// * this diagnostic fix trigger range touches the input cursor position |
239 | /// * that the contents of the file containing the cursor match `after` after the diagnostic fix is applied | 204 | /// * that the contents of the file containing the cursor match `after` after the diagnostic fix is applied |
240 | fn check_fix(ra_fixture_before: &str, ra_fixture_after: &str) { | 205 | pub(super) fn check_fix(ra_fixture_before: &str, ra_fixture_after: &str) { |
241 | let after = trim_indent(ra_fixture_after); | 206 | let after = trim_indent(ra_fixture_after); |
242 | 207 | ||
243 | let (analysis, file_position) = fixture::position(ra_fixture_before); | 208 | let (analysis, file_position) = fixture::position(ra_fixture_before); |
@@ -319,7 +284,7 @@ mod tests { | |||
319 | 284 | ||
320 | /// Takes a multi-file input fixture with annotated cursor position and checks that no diagnostics | 285 | /// Takes a multi-file input fixture with annotated cursor position and checks that no diagnostics |
321 | /// apply to the file containing the cursor. | 286 | /// apply to the file containing the cursor. |
322 | fn check_no_diagnostics(ra_fixture: &str) { | 287 | pub(crate) fn check_no_diagnostics(ra_fixture: &str) { |
323 | let (analysis, files) = fixture::files(ra_fixture); | 288 | let (analysis, files) = fixture::files(ra_fixture); |
324 | let diagnostics = files | 289 | let diagnostics = files |
325 | .into_iter() | 290 | .into_iter() |
@@ -720,58 +685,6 @@ mod a { | |||
720 | } | 685 | } |
721 | 686 | ||
722 | #[test] | 687 | #[test] |
723 | fn test_check_struct_shorthand_initialization() { | ||
724 | check_no_diagnostics( | ||
725 | r#" | ||
726 | struct A { a: &'static str } | ||
727 | fn main() { A { a: "hello" } } | ||
728 | "#, | ||
729 | ); | ||
730 | check_no_diagnostics( | ||
731 | r#" | ||
732 | struct A(usize); | ||
733 | fn main() { A { 0: 0 } } | ||
734 | "#, | ||
735 | ); | ||
736 | |||
737 | check_fix( | ||
738 | r#" | ||
739 | struct A { a: &'static str } | ||
740 | fn main() { | ||
741 | let a = "haha"; | ||
742 | A { a<|>: a } | ||
743 | } | ||
744 | "#, | ||
745 | r#" | ||
746 | struct A { a: &'static str } | ||
747 | fn main() { | ||
748 | let a = "haha"; | ||
749 | A { a } | ||
750 | } | ||
751 | "#, | ||
752 | ); | ||
753 | |||
754 | check_fix( | ||
755 | r#" | ||
756 | struct A { a: &'static str, b: &'static str } | ||
757 | fn main() { | ||
758 | let a = "haha"; | ||
759 | let b = "bb"; | ||
760 | A { a<|>: a, b } | ||
761 | } | ||
762 | "#, | ||
763 | r#" | ||
764 | struct A { a: &'static str, b: &'static str } | ||
765 | fn main() { | ||
766 | let a = "haha"; | ||
767 | let b = "bb"; | ||
768 | A { a, b } | ||
769 | } | ||
770 | "#, | ||
771 | ); | ||
772 | } | ||
773 | |||
774 | #[test] | ||
775 | fn test_add_field_from_usage() { | 688 | fn test_add_field_from_usage() { |
776 | check_fix( | 689 | check_fix( |
777 | r" | 690 | r" |
diff --git a/crates/ide/src/diagnostics/field_shorthand.rs b/crates/ide/src/diagnostics/field_shorthand.rs new file mode 100644 index 000000000..2c4acd783 --- /dev/null +++ b/crates/ide/src/diagnostics/field_shorthand.rs | |||
@@ -0,0 +1,206 @@ | |||
1 | //! Suggests shortening `Foo { field: field }` to `Foo { field }` in both | ||
2 | //! expressions and patterns. | ||
3 | |||
4 | use base_db::FileId; | ||
5 | use ide_db::source_change::SourceFileEdit; | ||
6 | use syntax::{ast, match_ast, AstNode, SyntaxNode}; | ||
7 | use text_edit::TextEdit; | ||
8 | |||
9 | use crate::{Diagnostic, Fix, Severity}; | ||
10 | |||
11 | pub(super) fn check(acc: &mut Vec<Diagnostic>, file_id: FileId, node: &SyntaxNode) { | ||
12 | match_ast! { | ||
13 | match node { | ||
14 | ast::RecordExpr(it) => check_expr_field_shorthand(acc, file_id, it), | ||
15 | ast::RecordPat(it) => check_pat_field_shorthand(acc, file_id, it), | ||
16 | _ => () | ||
17 | } | ||
18 | }; | ||
19 | } | ||
20 | |||
21 | fn check_expr_field_shorthand( | ||
22 | acc: &mut Vec<Diagnostic>, | ||
23 | file_id: FileId, | ||
24 | record_expr: ast::RecordExpr, | ||
25 | ) { | ||
26 | let record_field_list = match record_expr.record_expr_field_list() { | ||
27 | Some(it) => it, | ||
28 | None => return, | ||
29 | }; | ||
30 | for record_field in record_field_list.fields() { | ||
31 | let (name_ref, expr) = match record_field.name_ref().zip(record_field.expr()) { | ||
32 | Some(it) => it, | ||
33 | None => continue, | ||
34 | }; | ||
35 | |||
36 | let field_name = name_ref.syntax().text().to_string(); | ||
37 | let field_expr = expr.syntax().text().to_string(); | ||
38 | let field_name_is_tup_index = name_ref.as_tuple_field().is_some(); | ||
39 | if field_name != field_expr || field_name_is_tup_index { | ||
40 | continue; | ||
41 | } | ||
42 | |||
43 | let mut edit_builder = TextEdit::builder(); | ||
44 | edit_builder.delete(record_field.syntax().text_range()); | ||
45 | edit_builder.insert(record_field.syntax().text_range().start(), field_name); | ||
46 | let edit = edit_builder.finish(); | ||
47 | |||
48 | let field_range = record_field.syntax().text_range(); | ||
49 | acc.push(Diagnostic { | ||
50 | // name: None, | ||
51 | range: field_range, | ||
52 | message: "Shorthand struct initialization".to_string(), | ||
53 | severity: Severity::WeakWarning, | ||
54 | fix: Some(Fix::new( | ||
55 | "Use struct shorthand initialization", | ||
56 | SourceFileEdit { file_id, edit }.into(), | ||
57 | field_range, | ||
58 | )), | ||
59 | }); | ||
60 | } | ||
61 | } | ||
62 | |||
63 | fn check_pat_field_shorthand( | ||
64 | acc: &mut Vec<Diagnostic>, | ||
65 | file_id: FileId, | ||
66 | record_pat: ast::RecordPat, | ||
67 | ) { | ||
68 | let record_pat_field_list = match record_pat.record_pat_field_list() { | ||
69 | Some(it) => it, | ||
70 | None => return, | ||
71 | }; | ||
72 | for record_pat_field in record_pat_field_list.fields() { | ||
73 | let (name_ref, pat) = match record_pat_field.name_ref().zip(record_pat_field.pat()) { | ||
74 | Some(it) => it, | ||
75 | None => continue, | ||
76 | }; | ||
77 | |||
78 | let field_name = name_ref.syntax().text().to_string(); | ||
79 | let field_pat = pat.syntax().text().to_string(); | ||
80 | let field_name_is_tup_index = name_ref.as_tuple_field().is_some(); | ||
81 | if field_name != field_pat || field_name_is_tup_index { | ||
82 | continue; | ||
83 | } | ||
84 | |||
85 | let mut edit_builder = TextEdit::builder(); | ||
86 | edit_builder.delete(record_pat_field.syntax().text_range()); | ||
87 | edit_builder.insert(record_pat_field.syntax().text_range().start(), field_name); | ||
88 | let edit = edit_builder.finish(); | ||
89 | |||
90 | let field_range = record_pat_field.syntax().text_range(); | ||
91 | acc.push(Diagnostic { | ||
92 | // name: None, | ||
93 | range: field_range, | ||
94 | message: "Shorthand struct pattern".to_string(), | ||
95 | severity: Severity::WeakWarning, | ||
96 | fix: Some(Fix::new( | ||
97 | "Use struct field shorthand", | ||
98 | SourceFileEdit { file_id, edit }.into(), | ||
99 | field_range, | ||
100 | )), | ||
101 | }); | ||
102 | } | ||
103 | } | ||
104 | |||
105 | #[cfg(test)] | ||
106 | mod tests { | ||
107 | use crate::diagnostics::tests::{check_fix, check_no_diagnostics}; | ||
108 | |||
109 | #[test] | ||
110 | fn test_check_expr_field_shorthand() { | ||
111 | check_no_diagnostics( | ||
112 | r#" | ||
113 | struct A { a: &'static str } | ||
114 | fn main() { A { a: "hello" } } | ||
115 | "#, | ||
116 | ); | ||
117 | check_no_diagnostics( | ||
118 | r#" | ||
119 | struct A(usize); | ||
120 | fn main() { A { 0: 0 } } | ||
121 | "#, | ||
122 | ); | ||
123 | |||
124 | check_fix( | ||
125 | r#" | ||
126 | struct A { a: &'static str } | ||
127 | fn main() { | ||
128 | let a = "haha"; | ||
129 | A { a<|>: a } | ||
130 | } | ||
131 | "#, | ||
132 | r#" | ||
133 | struct A { a: &'static str } | ||
134 | fn main() { | ||
135 | let a = "haha"; | ||
136 | A { a } | ||
137 | } | ||
138 | "#, | ||
139 | ); | ||
140 | |||
141 | check_fix( | ||
142 | r#" | ||
143 | struct A { a: &'static str, b: &'static str } | ||
144 | fn main() { | ||
145 | let a = "haha"; | ||
146 | let b = "bb"; | ||
147 | A { a<|>: a, b } | ||
148 | } | ||
149 | "#, | ||
150 | r#" | ||
151 | struct A { a: &'static str, b: &'static str } | ||
152 | fn main() { | ||
153 | let a = "haha"; | ||
154 | let b = "bb"; | ||
155 | A { a, b } | ||
156 | } | ||
157 | "#, | ||
158 | ); | ||
159 | } | ||
160 | |||
161 | #[test] | ||
162 | fn test_check_pat_field_shorthand() { | ||
163 | check_no_diagnostics( | ||
164 | r#" | ||
165 | struct A { a: &'static str } | ||
166 | fn f(a: A) { let A { a: hello } = a; } | ||
167 | "#, | ||
168 | ); | ||
169 | check_no_diagnostics( | ||
170 | r#" | ||
171 | struct A(usize); | ||
172 | fn f(a: A) { let A { 0: 0 } = a; } | ||
173 | "#, | ||
174 | ); | ||
175 | |||
176 | check_fix( | ||
177 | r#" | ||
178 | struct A { a: &'static str } | ||
179 | fn f(a: A) { | ||
180 | let A { a<|>: a } = a; | ||
181 | } | ||
182 | "#, | ||
183 | r#" | ||
184 | struct A { a: &'static str } | ||
185 | fn f(a: A) { | ||
186 | let A { a } = a; | ||
187 | } | ||
188 | "#, | ||
189 | ); | ||
190 | |||
191 | check_fix( | ||
192 | r#" | ||
193 | struct A { a: &'static str, b: &'static str } | ||
194 | fn f(a: A) { | ||
195 | let A { a<|>: a, b } = a; | ||
196 | } | ||
197 | "#, | ||
198 | r#" | ||
199 | struct A { a: &'static str, b: &'static str } | ||
200 | fn f(a: A) { | ||
201 | let A { a, b } = a; | ||
202 | } | ||
203 | "#, | ||
204 | ); | ||
205 | } | ||
206 | } | ||
diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs index 06af36b73..d9dc63b33 100644 --- a/crates/ide/src/doc_links.rs +++ b/crates/ide/src/doc_links.rs | |||
@@ -1,9 +1,10 @@ | |||
1 | //! Resolves and rewrites links in markdown documentation. | 1 | //! Resolves and rewrites links in markdown documentation. |
2 | 2 | ||
3 | use std::convert::TryFrom; | ||
3 | use std::iter::once; | 4 | use std::iter::once; |
4 | 5 | ||
5 | use itertools::Itertools; | 6 | use itertools::Itertools; |
6 | use pulldown_cmark::{CowStr, Event, LinkType, Options, Parser, Tag}; | 7 | use pulldown_cmark::{BrokenLink, CowStr, Event, InlineStr, LinkType, Options, Parser, Tag}; |
7 | use pulldown_cmark_to_cmark::{cmark_with_options, Options as CmarkOptions}; | 8 | use pulldown_cmark_to_cmark::{cmark_with_options, Options as CmarkOptions}; |
8 | use url::Url; | 9 | use url::Url; |
9 | 10 | ||
@@ -13,7 +14,7 @@ use hir::{ | |||
13 | ModuleDef, | 14 | ModuleDef, |
14 | }; | 15 | }; |
15 | use ide_db::{ | 16 | use ide_db::{ |
16 | defs::{classify_name, classify_name_ref, Definition}, | 17 | defs::{Definition, NameClass, NameRefClass}, |
17 | RootDatabase, | 18 | RootDatabase, |
18 | }; | 19 | }; |
19 | use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset, T}; | 20 | use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset, T}; |
@@ -24,11 +25,13 @@ pub type DocumentationLink = String; | |||
24 | 25 | ||
25 | /// Rewrite documentation links in markdown to point to an online host (e.g. docs.rs) | 26 | /// Rewrite documentation links in markdown to point to an online host (e.g. docs.rs) |
26 | pub fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> String { | 27 | pub fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> String { |
27 | let doc = Parser::new_with_broken_link_callback( | 28 | let mut cb = |link: BrokenLink| { |
28 | markdown, | 29 | Some(( |
29 | Options::empty(), | 30 | /*url*/ link.reference.to_owned().into(), |
30 | Some(&|label, _| Some((/*url*/ label.to_string(), /*title*/ label.to_string()))), | 31 | /*title*/ link.reference.to_owned().into(), |
31 | ); | 32 | )) |
33 | }; | ||
34 | let doc = Parser::new_with_broken_link_callback(markdown, Options::empty(), Some(&mut cb)); | ||
32 | 35 | ||
33 | let doc = map_links(doc, |target, title: &str| { | 36 | let doc = map_links(doc, |target, title: &str| { |
34 | // This check is imperfect, there's some overlap between valid intra-doc links | 37 | // This check is imperfect, there's some overlap between valid intra-doc links |
@@ -66,11 +69,11 @@ pub fn remove_links(markdown: &str) -> String { | |||
66 | let mut opts = Options::empty(); | 69 | let mut opts = Options::empty(); |
67 | opts.insert(Options::ENABLE_FOOTNOTES); | 70 | opts.insert(Options::ENABLE_FOOTNOTES); |
68 | 71 | ||
69 | let doc = Parser::new_with_broken_link_callback( | 72 | let mut cb = |_: BrokenLink| { |
70 | markdown, | 73 | let empty = InlineStr::try_from("").unwrap(); |
71 | opts, | 74 | Some((CowStr::Inlined(empty.clone()), CowStr::Inlined(empty))) |
72 | Some(&|_, _| Some((String::new(), String::new()))), | 75 | }; |
73 | ); | 76 | let doc = Parser::new_with_broken_link_callback(markdown, opts, Some(&mut cb)); |
74 | let doc = doc.filter_map(move |evt| match evt { | 77 | let doc = doc.filter_map(move |evt| match evt { |
75 | Event::Start(Tag::Link(link_type, ref target, ref title)) => { | 78 | Event::Start(Tag::Link(link_type, ref target, ref title)) => { |
76 | if link_type == LinkType::Inline && target.contains("://") { | 79 | if link_type == LinkType::Inline && target.contains("://") { |
@@ -229,8 +232,8 @@ pub(crate) fn external_docs( | |||
229 | let node = token.parent(); | 232 | let node = token.parent(); |
230 | let definition = match_ast! { | 233 | let definition = match_ast! { |
231 | match node { | 234 | match node { |
232 | ast::NameRef(name_ref) => classify_name_ref(&sema, &name_ref).map(|d| d.definition(sema.db)), | 235 | ast::NameRef(name_ref) => NameRefClass::classify(&sema, &name_ref).map(|d| d.referenced(sema.db)), |
233 | ast::Name(name) => classify_name(&sema, &name).map(|d| d.definition(sema.db)), | 236 | ast::Name(name) => NameClass::classify(&sema, &name).map(|d| d.referenced_or_defined(sema.db)), |
234 | _ => None, | 237 | _ => None, |
235 | } | 238 | } |
236 | }; | 239 | }; |
diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index 582bf4837..a87e31019 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs | |||
@@ -1,6 +1,6 @@ | |||
1 | use hir::Semantics; | 1 | use hir::Semantics; |
2 | use ide_db::{ | 2 | use ide_db::{ |
3 | defs::{classify_name, classify_name_ref}, | 3 | defs::{NameClass, NameRefClass}, |
4 | symbol_index, RootDatabase, | 4 | symbol_index, RootDatabase, |
5 | }; | 5 | }; |
6 | use syntax::{ | 6 | use syntax::{ |
@@ -40,7 +40,7 @@ pub(crate) fn goto_definition( | |||
40 | reference_definition(&sema, &name_ref).to_vec() | 40 | reference_definition(&sema, &name_ref).to_vec() |
41 | }, | 41 | }, |
42 | ast::Name(name) => { | 42 | ast::Name(name) => { |
43 | let def = classify_name(&sema, &name)?.definition(sema.db); | 43 | let def = NameClass::classify(&sema, &name)?.referenced_or_defined(sema.db); |
44 | let nav = def.try_to_nav(sema.db)?; | 44 | let nav = def.try_to_nav(sema.db)?; |
45 | vec![nav] | 45 | vec![nav] |
46 | }, | 46 | }, |
@@ -81,9 +81,9 @@ pub(crate) fn reference_definition( | |||
81 | sema: &Semantics<RootDatabase>, | 81 | sema: &Semantics<RootDatabase>, |
82 | name_ref: &ast::NameRef, | 82 | name_ref: &ast::NameRef, |
83 | ) -> ReferenceResult { | 83 | ) -> ReferenceResult { |
84 | let name_kind = classify_name_ref(sema, name_ref); | 84 | let name_kind = NameRefClass::classify(sema, name_ref); |
85 | if let Some(def) = name_kind { | 85 | if let Some(def) = name_kind { |
86 | let def = def.definition(sema.db); | 86 | let def = def.referenced(sema.db); |
87 | return match def.try_to_nav(sema.db) { | 87 | return match def.try_to_nav(sema.db) { |
88 | Some(nav) => ReferenceResult::Exact(nav), | 88 | Some(nav) => ReferenceResult::Exact(nav), |
89 | None => ReferenceResult::Approximate(Vec::new()), | 89 | None => ReferenceResult::Approximate(Vec::new()), |
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index 6290b35bd..845333e2a 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs | |||
@@ -4,7 +4,7 @@ use hir::{ | |||
4 | Module, ModuleDef, ModuleSource, Semantics, | 4 | Module, ModuleDef, ModuleSource, Semantics, |
5 | }; | 5 | }; |
6 | use ide_db::{ | 6 | use ide_db::{ |
7 | defs::{classify_name, classify_name_ref, Definition}, | 7 | defs::{Definition, NameClass, NameRefClass}, |
8 | RootDatabase, | 8 | RootDatabase, |
9 | }; | 9 | }; |
10 | use itertools::Itertools; | 10 | use itertools::Itertools; |
@@ -107,8 +107,8 @@ pub(crate) fn hover( | |||
107 | let node = token.parent(); | 107 | let node = token.parent(); |
108 | let definition = match_ast! { | 108 | let definition = match_ast! { |
109 | match node { | 109 | match node { |
110 | ast::NameRef(name_ref) => classify_name_ref(&sema, &name_ref).map(|d| d.definition(sema.db)), | 110 | ast::Name(name) => NameClass::classify(&sema, &name).and_then(|d| d.defined(sema.db)), |
111 | ast::Name(name) => classify_name(&sema, &name).map(|d| d.definition(sema.db)), | 111 | ast::NameRef(name_ref) => NameRefClass::classify(&sema, &name_ref).map(|d| d.referenced(sema.db)), |
112 | _ => None, | 112 | _ => None, |
113 | } | 113 | } |
114 | }; | 114 | }; |
@@ -3232,4 +3232,27 @@ fn main() { let foo_test = name_with_dashes::wrapper::Thing::new<|>(); } | |||
3232 | "#]], | 3232 | "#]], |
3233 | ) | 3233 | ) |
3234 | } | 3234 | } |
3235 | |||
3236 | #[test] | ||
3237 | fn hover_field_pat_shorthand_ref_match_ergonomics() { | ||
3238 | check( | ||
3239 | r#" | ||
3240 | struct S { | ||
3241 | f: i32, | ||
3242 | } | ||
3243 | |||
3244 | fn main() { | ||
3245 | let s = S { f: 0 }; | ||
3246 | let S { f<|> } = &s; | ||
3247 | } | ||
3248 | "#, | ||
3249 | expect![[r#" | ||
3250 | *f* | ||
3251 | |||
3252 | ```rust | ||
3253 | &i32 | ||
3254 | ``` | ||
3255 | "#]], | ||
3256 | ); | ||
3257 | } | ||
3235 | } | 3258 | } |
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs index 88e2f2db3..67ec257a8 100644 --- a/crates/ide/src/references.rs +++ b/crates/ide/src/references.rs | |||
@@ -13,7 +13,7 @@ pub(crate) mod rename; | |||
13 | 13 | ||
14 | use hir::Semantics; | 14 | use hir::Semantics; |
15 | use ide_db::{ | 15 | use ide_db::{ |
16 | defs::{classify_name, classify_name_ref, Definition}, | 16 | defs::{Definition, NameClass, NameRefClass}, |
17 | search::SearchScope, | 17 | search::SearchScope, |
18 | RootDatabase, | 18 | RootDatabase, |
19 | }; | 19 | }; |
@@ -132,13 +132,13 @@ fn find_name( | |||
132 | opt_name: Option<ast::Name>, | 132 | opt_name: Option<ast::Name>, |
133 | ) -> Option<RangeInfo<Definition>> { | 133 | ) -> Option<RangeInfo<Definition>> { |
134 | if let Some(name) = opt_name { | 134 | if let Some(name) = opt_name { |
135 | let def = classify_name(sema, &name)?.definition(sema.db); | 135 | let def = NameClass::classify(sema, &name)?.referenced_or_defined(sema.db); |
136 | let range = name.syntax().text_range(); | 136 | let range = name.syntax().text_range(); |
137 | return Some(RangeInfo::new(range, def)); | 137 | return Some(RangeInfo::new(range, def)); |
138 | } | 138 | } |
139 | let name_ref = | 139 | let name_ref = |
140 | sema.find_node_at_offset_with_descend::<ast::NameRef>(&syntax, position.offset)?; | 140 | sema.find_node_at_offset_with_descend::<ast::NameRef>(&syntax, position.offset)?; |
141 | let def = classify_name_ref(sema, &name_ref)?.definition(sema.db); | 141 | let def = NameRefClass::classify(sema, &name_ref)?.referenced(sema.db); |
142 | let range = name_ref.syntax().text_range(); | 142 | let range = name_ref.syntax().text_range(); |
143 | Some(RangeInfo::new(range, def)) | 143 | Some(RangeInfo::new(range, def)) |
144 | } | 144 | } |
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs index f9a11e43d..35aafc49d 100644 --- a/crates/ide/src/references/rename.rs +++ b/crates/ide/src/references/rename.rs | |||
@@ -3,7 +3,7 @@ | |||
3 | use base_db::SourceDatabaseExt; | 3 | use base_db::SourceDatabaseExt; |
4 | use hir::{Module, ModuleDef, ModuleSource, Semantics}; | 4 | use hir::{Module, ModuleDef, ModuleSource, Semantics}; |
5 | use ide_db::{ | 5 | use ide_db::{ |
6 | defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass}, | 6 | defs::{Definition, NameClass, NameRefClass}, |
7 | RootDatabase, | 7 | RootDatabase, |
8 | }; | 8 | }; |
9 | 9 | ||
@@ -88,13 +88,13 @@ fn find_module_at_offset( | |||
88 | let module = match_ast! { | 88 | let module = match_ast! { |
89 | match (ident.parent()) { | 89 | match (ident.parent()) { |
90 | ast::NameRef(name_ref) => { | 90 | ast::NameRef(name_ref) => { |
91 | match classify_name_ref(sema, &name_ref)? { | 91 | match NameRefClass::classify(sema, &name_ref)? { |
92 | NameRefClass::Definition(Definition::ModuleDef(ModuleDef::Module(module))) => module, | 92 | NameRefClass::Definition(Definition::ModuleDef(ModuleDef::Module(module))) => module, |
93 | _ => return None, | 93 | _ => return None, |
94 | } | 94 | } |
95 | }, | 95 | }, |
96 | ast::Name(name) => { | 96 | ast::Name(name) => { |
97 | match classify_name(&sema, &name)? { | 97 | match NameClass::classify(&sema, &name)? { |
98 | NameClass::Definition(Definition::ModuleDef(ModuleDef::Module(module))) => module, | 98 | NameClass::Definition(Definition::ModuleDef(ModuleDef::Module(module))) => module, |
99 | _ => return None, | 99 | _ => return None, |
100 | } | 100 | } |
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs index 6aafd6fd5..b35c03162 100644 --- a/crates/ide/src/syntax_highlighting.rs +++ b/crates/ide/src/syntax_highlighting.rs | |||
@@ -1,12 +1,14 @@ | |||
1 | mod tags; | 1 | mod format; |
2 | mod html; | 2 | mod html; |
3 | mod injection; | 3 | mod injection; |
4 | mod macro_rules; | ||
5 | mod tags; | ||
4 | #[cfg(test)] | 6 | #[cfg(test)] |
5 | mod tests; | 7 | mod tests; |
6 | 8 | ||
7 | use hir::{Local, Name, Semantics, VariantDef}; | 9 | use hir::{Local, Name, Semantics, VariantDef}; |
8 | use ide_db::{ | 10 | use ide_db::{ |
9 | defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass}, | 11 | defs::{Definition, NameClass, NameRefClass}, |
10 | RootDatabase, | 12 | RootDatabase, |
11 | }; | 13 | }; |
12 | use rustc_hash::FxHashMap; | 14 | use rustc_hash::FxHashMap; |
@@ -17,9 +19,11 @@ use syntax::{ | |||
17 | SyntaxNode, SyntaxToken, TextRange, WalkEvent, T, | 19 | SyntaxNode, SyntaxToken, TextRange, WalkEvent, T, |
18 | }; | 20 | }; |
19 | 21 | ||
20 | use crate::FileId; | 22 | use crate::{ |
23 | syntax_highlighting::{format::FormatStringHighlighter, macro_rules::MacroRulesHighlighter}, | ||
24 | FileId, | ||
25 | }; | ||
21 | 26 | ||
22 | use ast::FormatSpecifier; | ||
23 | pub(crate) use html::highlight_as_html; | 27 | pub(crate) use html::highlight_as_html; |
24 | pub use tags::{Highlight, HighlightModifier, HighlightModifiers, HighlightTag}; | 28 | pub use tags::{Highlight, HighlightModifier, HighlightModifiers, HighlightTag}; |
25 | 29 | ||
@@ -68,8 +72,9 @@ pub(crate) fn highlight( | |||
68 | // When we leave a node, the we use it to flatten the highlighted ranges. | 72 | // When we leave a node, the we use it to flatten the highlighted ranges. |
69 | let mut stack = HighlightedRangeStack::new(); | 73 | let mut stack = HighlightedRangeStack::new(); |
70 | 74 | ||
71 | let mut current_macro_call: Option<(ast::MacroCall, Option<MacroMatcherParseState>)> = None; | 75 | let mut current_macro_call: Option<ast::MacroCall> = None; |
72 | let mut format_string: Option<SyntaxElement> = None; | 76 | let mut format_string_highlighter = FormatStringHighlighter::default(); |
77 | let mut macro_rules_highlighter = MacroRulesHighlighter::default(); | ||
73 | 78 | ||
74 | // Walk all nodes, keeping track of whether we are inside a macro or not. | 79 | // Walk all nodes, keeping track of whether we are inside a macro or not. |
75 | // If in macro, expand it first and highlight the expanded code. | 80 | // If in macro, expand it first and highlight the expanded code. |
@@ -99,9 +104,8 @@ pub(crate) fn highlight( | |||
99 | binding_hash: None, | 104 | binding_hash: None, |
100 | }); | 105 | }); |
101 | } | 106 | } |
102 | let mut is_macro_rules = None; | ||
103 | if let Some(name) = mc.is_macro_rules() { | 107 | if let Some(name) = mc.is_macro_rules() { |
104 | is_macro_rules = Some(MacroMatcherParseState::new()); | 108 | macro_rules_highlighter.init(); |
105 | if let Some((highlight, binding_hash)) = highlight_element( | 109 | if let Some((highlight, binding_hash)) = highlight_element( |
106 | &sema, | 110 | &sema, |
107 | &mut bindings_shadow_count, | 111 | &mut bindings_shadow_count, |
@@ -115,13 +119,14 @@ pub(crate) fn highlight( | |||
115 | }); | 119 | }); |
116 | } | 120 | } |
117 | } | 121 | } |
118 | current_macro_call = Some((mc.clone(), is_macro_rules)); | 122 | current_macro_call = Some(mc.clone()); |
119 | continue; | 123 | continue; |
120 | } | 124 | } |
121 | WalkEvent::Leave(Some(mc)) => { | 125 | WalkEvent::Leave(Some(mc)) => { |
122 | assert!(current_macro_call.map(|it| it.0) == Some(mc)); | 126 | assert!(current_macro_call == Some(mc)); |
123 | current_macro_call = None; | 127 | current_macro_call = None; |
124 | format_string = None; | 128 | format_string_highlighter = FormatStringHighlighter::default(); |
129 | macro_rules_highlighter = MacroRulesHighlighter::default(); | ||
125 | } | 130 | } |
126 | _ => (), | 131 | _ => (), |
127 | } | 132 | } |
@@ -148,20 +153,6 @@ pub(crate) fn highlight( | |||
148 | WalkEvent::Leave(_) => continue, | 153 | WalkEvent::Leave(_) => continue, |
149 | }; | 154 | }; |
150 | 155 | ||
151 | // check if in matcher part of a macro_rules rule | ||
152 | if let Some((_, Some(ref mut state))) = current_macro_call { | ||
153 | if let Some(tok) = element.as_token() { | ||
154 | if matches!( | ||
155 | update_macro_rules_state(tok, state), | ||
156 | RuleState::Matcher | RuleState::Expander | ||
157 | ) { | ||
158 | if skip_metavariables(element.clone()) { | ||
159 | continue; | ||
160 | } | ||
161 | } | ||
162 | } | ||
163 | } | ||
164 | |||
165 | let range = element.text_range(); | 156 | let range = element.text_range(); |
166 | 157 | ||
167 | let element_to_highlight = if current_macro_call.is_some() && element.kind() != COMMENT { | 158 | let element_to_highlight = if current_macro_call.is_some() && element.kind() != COMMENT { |
@@ -173,29 +164,9 @@ pub(crate) fn highlight( | |||
173 | let token = sema.descend_into_macros(token.clone()); | 164 | let token = sema.descend_into_macros(token.clone()); |
174 | let parent = token.parent(); | 165 | let parent = token.parent(); |
175 | 166 | ||
176 | // Check if macro takes a format string and remember it for highlighting later. | 167 | format_string_highlighter.check_for_format_string(&parent); |
177 | // The macros that accept a format string expand to a compiler builtin macros | 168 | if let Some(tok) = element.as_token() { |
178 | // `format_args` and `format_args_nl`. | 169 | macro_rules_highlighter.advance(tok); |
179 | if let Some(name) = parent | ||
180 | .parent() | ||
181 | .and_then(ast::MacroCall::cast) | ||
182 | .and_then(|mc| mc.path()) | ||
183 | .and_then(|p| p.segment()) | ||
184 | .and_then(|s| s.name_ref()) | ||
185 | { | ||
186 | match name.text().as_str() { | ||
187 | "format_args" | "format_args_nl" => { | ||
188 | format_string = parent | ||
189 | .children_with_tokens() | ||
190 | .filter(|t| t.kind() != WHITESPACE) | ||
191 | .nth(1) | ||
192 | .filter(|e| { | ||
193 | ast::String::can_cast(e.kind()) | ||
194 | || ast::RawString::can_cast(e.kind()) | ||
195 | }) | ||
196 | } | ||
197 | _ => {} | ||
198 | } | ||
199 | } | 170 | } |
200 | 171 | ||
201 | // We only care Name and Name_ref | 172 | // We only care Name and Name_ref |
@@ -214,31 +185,20 @@ pub(crate) fn highlight( | |||
214 | } | 185 | } |
215 | } | 186 | } |
216 | 187 | ||
217 | let is_format_string = format_string.as_ref() == Some(&element_to_highlight); | ||
218 | |||
219 | if let Some((highlight, binding_hash)) = highlight_element( | 188 | if let Some((highlight, binding_hash)) = highlight_element( |
220 | &sema, | 189 | &sema, |
221 | &mut bindings_shadow_count, | 190 | &mut bindings_shadow_count, |
222 | syntactic_name_ref_highlighting, | 191 | syntactic_name_ref_highlighting, |
223 | element_to_highlight.clone(), | 192 | element_to_highlight.clone(), |
224 | ) { | 193 | ) { |
225 | stack.add(HighlightedRange { range, highlight, binding_hash }); | 194 | if macro_rules_highlighter.highlight(element_to_highlight.clone()).is_none() { |
195 | stack.add(HighlightedRange { range, highlight, binding_hash }); | ||
196 | } | ||
197 | |||
226 | if let Some(string) = | 198 | if let Some(string) = |
227 | element_to_highlight.as_token().cloned().and_then(ast::String::cast) | 199 | element_to_highlight.as_token().cloned().and_then(ast::String::cast) |
228 | { | 200 | { |
229 | if is_format_string { | 201 | format_string_highlighter.highlight_format_string(&mut stack, &string, range); |
230 | stack.push(); | ||
231 | string.lex_format_specifier(|piece_range, kind| { | ||
232 | if let Some(highlight) = highlight_format_specifier(kind) { | ||
233 | stack.add(HighlightedRange { | ||
234 | range: piece_range + range.start(), | ||
235 | highlight: highlight.into(), | ||
236 | binding_hash: None, | ||
237 | }); | ||
238 | } | ||
239 | }); | ||
240 | stack.pop(); | ||
241 | } | ||
242 | // Highlight escape sequences | 202 | // Highlight escape sequences |
243 | if let Some(char_ranges) = string.char_ranges() { | 203 | if let Some(char_ranges) = string.char_ranges() { |
244 | stack.push(); | 204 | stack.push(); |
@@ -256,19 +216,7 @@ pub(crate) fn highlight( | |||
256 | } else if let Some(string) = | 216 | } else if let Some(string) = |
257 | element_to_highlight.as_token().cloned().and_then(ast::RawString::cast) | 217 | element_to_highlight.as_token().cloned().and_then(ast::RawString::cast) |
258 | { | 218 | { |
259 | if is_format_string { | 219 | format_string_highlighter.highlight_format_string(&mut stack, &string, range); |
260 | stack.push(); | ||
261 | string.lex_format_specifier(|piece_range, kind| { | ||
262 | if let Some(highlight) = highlight_format_specifier(kind) { | ||
263 | stack.add(HighlightedRange { | ||
264 | range: piece_range + range.start(), | ||
265 | highlight: highlight.into(), | ||
266 | binding_hash: None, | ||
267 | }); | ||
268 | } | ||
269 | }); | ||
270 | stack.pop(); | ||
271 | } | ||
272 | } | 220 | } |
273 | } | 221 | } |
274 | } | 222 | } |
@@ -436,24 +384,6 @@ impl HighlightedRangeStack { | |||
436 | } | 384 | } |
437 | } | 385 | } |
438 | 386 | ||
439 | fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HighlightTag> { | ||
440 | Some(match kind { | ||
441 | FormatSpecifier::Open | ||
442 | | FormatSpecifier::Close | ||
443 | | FormatSpecifier::Colon | ||
444 | | FormatSpecifier::Fill | ||
445 | | FormatSpecifier::Align | ||
446 | | FormatSpecifier::Sign | ||
447 | | FormatSpecifier::NumberSign | ||
448 | | FormatSpecifier::DollarSign | ||
449 | | FormatSpecifier::Dot | ||
450 | | FormatSpecifier::Asterisk | ||
451 | | FormatSpecifier::QuestionMark => HighlightTag::FormatSpecifier, | ||
452 | FormatSpecifier::Integer | FormatSpecifier::Zero => HighlightTag::NumericLiteral, | ||
453 | FormatSpecifier::Identifier => HighlightTag::Local, | ||
454 | }) | ||
455 | } | ||
456 | |||
457 | fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> { | 387 | fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> { |
458 | let path = macro_call.path()?; | 388 | let path = macro_call.path()?; |
459 | let name_ref = path.segment()?.name_ref()?; | 389 | let name_ref = path.segment()?.name_ref()?; |
@@ -513,7 +443,7 @@ fn highlight_element( | |||
513 | // Highlight definitions depending on the "type" of the definition. | 443 | // Highlight definitions depending on the "type" of the definition. |
514 | NAME => { | 444 | NAME => { |
515 | let name = element.into_node().and_then(ast::Name::cast).unwrap(); | 445 | let name = element.into_node().and_then(ast::Name::cast).unwrap(); |
516 | let name_kind = classify_name(sema, &name); | 446 | let name_kind = NameClass::classify(sema, &name); |
517 | 447 | ||
518 | if let Some(NameClass::Definition(Definition::Local(local))) = &name_kind { | 448 | if let Some(NameClass::Definition(Definition::Local(local))) = &name_kind { |
519 | if let Some(name) = local.name(db) { | 449 | if let Some(name) = local.name(db) { |
@@ -529,9 +459,9 @@ fn highlight_element( | |||
529 | highlight_def(db, def) | HighlightModifier::Definition | 459 | highlight_def(db, def) | HighlightModifier::Definition |
530 | } | 460 | } |
531 | Some(NameClass::ConstReference(def)) => highlight_def(db, def), | 461 | Some(NameClass::ConstReference(def)) => highlight_def(db, def), |
532 | Some(NameClass::FieldShorthand { field, .. }) => { | 462 | Some(NameClass::PatFieldShorthand { field_ref, .. }) => { |
533 | let mut h = HighlightTag::Field.into(); | 463 | let mut h = HighlightTag::Field.into(); |
534 | if let Definition::Field(field) = field { | 464 | if let Definition::Field(field) = field_ref { |
535 | if let VariantDef::Union(_) = field.parent_def(db) { | 465 | if let VariantDef::Union(_) = field.parent_def(db) { |
536 | h |= HighlightModifier::Unsafe; | 466 | h |= HighlightModifier::Unsafe; |
537 | } | 467 | } |
@@ -550,7 +480,7 @@ fn highlight_element( | |||
550 | NAME_REF => { | 480 | NAME_REF => { |
551 | let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap(); | 481 | let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap(); |
552 | highlight_func_by_name_ref(sema, &name_ref).unwrap_or_else(|| { | 482 | highlight_func_by_name_ref(sema, &name_ref).unwrap_or_else(|| { |
553 | match classify_name_ref(sema, &name_ref) { | 483 | match NameRefClass::classify(sema, &name_ref) { |
554 | Some(name_kind) => match name_kind { | 484 | Some(name_kind) => match name_kind { |
555 | NameRefClass::ExternCrate(_) => HighlightTag::Module.into(), | 485 | NameRefClass::ExternCrate(_) => HighlightTag::Module.into(), |
556 | NameRefClass::Definition(def) => { | 486 | NameRefClass::Definition(def) => { |
@@ -934,99 +864,3 @@ fn highlight_name_ref_by_syntax(name: ast::NameRef, sema: &Semantics<RootDatabas | |||
934 | _ => default.into(), | 864 | _ => default.into(), |
935 | } | 865 | } |
936 | } | 866 | } |
937 | |||
938 | struct MacroMatcherParseState { | ||
939 | /// Opening and corresponding closing bracket of the matcher or expander of the current rule | ||
940 | paren_ty: Option<(SyntaxKind, SyntaxKind)>, | ||
941 | paren_level: usize, | ||
942 | rule_state: RuleState, | ||
943 | /// Whether we are inside the outer `{` `}` macro block that holds the rules | ||
944 | in_invoc_body: bool, | ||
945 | } | ||
946 | |||
947 | impl MacroMatcherParseState { | ||
948 | fn new() -> Self { | ||
949 | MacroMatcherParseState { | ||
950 | paren_ty: None, | ||
951 | paren_level: 0, | ||
952 | in_invoc_body: false, | ||
953 | rule_state: RuleState::None, | ||
954 | } | ||
955 | } | ||
956 | } | ||
957 | |||
958 | #[derive(Copy, Clone, PartialEq)] | ||
959 | enum RuleState { | ||
960 | Matcher, | ||
961 | Expander, | ||
962 | Between, | ||
963 | None, | ||
964 | } | ||
965 | |||
966 | impl RuleState { | ||
967 | fn transition(&mut self) { | ||
968 | *self = match self { | ||
969 | RuleState::Matcher => RuleState::Between, | ||
970 | RuleState::Expander => RuleState::None, | ||
971 | RuleState::Between => RuleState::Expander, | ||
972 | RuleState::None => RuleState::Matcher, | ||
973 | }; | ||
974 | } | ||
975 | } | ||
976 | |||
977 | fn update_macro_rules_state(tok: &SyntaxToken, state: &mut MacroMatcherParseState) -> RuleState { | ||
978 | if !state.in_invoc_body { | ||
979 | if tok.kind() == T!['{'] { | ||
980 | state.in_invoc_body = true; | ||
981 | } | ||
982 | return state.rule_state; | ||
983 | } | ||
984 | |||
985 | match state.paren_ty { | ||
986 | Some((open, close)) => { | ||
987 | if tok.kind() == open { | ||
988 | state.paren_level += 1; | ||
989 | } else if tok.kind() == close { | ||
990 | state.paren_level -= 1; | ||
991 | if state.paren_level == 0 { | ||
992 | let res = state.rule_state; | ||
993 | state.rule_state.transition(); | ||
994 | state.paren_ty = None; | ||
995 | return res; | ||
996 | } | ||
997 | } | ||
998 | } | ||
999 | None => { | ||
1000 | match tok.kind() { | ||
1001 | T!['('] => { | ||
1002 | state.paren_ty = Some((T!['('], T![')'])); | ||
1003 | } | ||
1004 | T!['{'] => { | ||
1005 | state.paren_ty = Some((T!['{'], T!['}'])); | ||
1006 | } | ||
1007 | T!['['] => { | ||
1008 | state.paren_ty = Some((T!['['], T![']'])); | ||
1009 | } | ||
1010 | _ => (), | ||
1011 | } | ||
1012 | if state.paren_ty.is_some() { | ||
1013 | state.paren_level = 1; | ||
1014 | state.rule_state.transition(); | ||
1015 | } | ||
1016 | } | ||
1017 | } | ||
1018 | state.rule_state | ||
1019 | } | ||
1020 | |||
1021 | fn skip_metavariables(element: SyntaxElement) -> bool { | ||
1022 | let tok = match element.as_token() { | ||
1023 | Some(tok) => tok, | ||
1024 | None => return false, | ||
1025 | }; | ||
1026 | let is_fragment = || tok.prev_token().map(|tok| tok.kind()) == Some(T![$]); | ||
1027 | match tok.kind() { | ||
1028 | IDENT if is_fragment() => true, | ||
1029 | kind if kind.is_keyword() && is_fragment() => true, | ||
1030 | _ => false, | ||
1031 | } | ||
1032 | } | ||
diff --git a/crates/ide/src/syntax_highlighting/format.rs b/crates/ide/src/syntax_highlighting/format.rs new file mode 100644 index 000000000..71bde24f0 --- /dev/null +++ b/crates/ide/src/syntax_highlighting/format.rs | |||
@@ -0,0 +1,78 @@ | |||
1 | //! Syntax highlighting for format macro strings. | ||
2 | use syntax::{ | ||
3 | ast::{self, FormatSpecifier, HasFormatSpecifier}, | ||
4 | AstNode, AstToken, SyntaxElement, SyntaxKind, SyntaxNode, TextRange, | ||
5 | }; | ||
6 | |||
7 | use crate::{syntax_highlighting::HighlightedRangeStack, HighlightTag, HighlightedRange}; | ||
8 | |||
9 | #[derive(Default)] | ||
10 | pub(super) struct FormatStringHighlighter { | ||
11 | format_string: Option<SyntaxElement>, | ||
12 | } | ||
13 | |||
14 | impl FormatStringHighlighter { | ||
15 | pub(super) fn check_for_format_string(&mut self, parent: &SyntaxNode) { | ||
16 | // Check if macro takes a format string and remember it for highlighting later. | ||
17 | // The macros that accept a format string expand to a compiler builtin macros | ||
18 | // `format_args` and `format_args_nl`. | ||
19 | if let Some(name) = parent | ||
20 | .parent() | ||
21 | .and_then(ast::MacroCall::cast) | ||
22 | .and_then(|mc| mc.path()) | ||
23 | .and_then(|p| p.segment()) | ||
24 | .and_then(|s| s.name_ref()) | ||
25 | { | ||
26 | match name.text().as_str() { | ||
27 | "format_args" | "format_args_nl" => { | ||
28 | self.format_string = parent | ||
29 | .children_with_tokens() | ||
30 | .filter(|t| t.kind() != SyntaxKind::WHITESPACE) | ||
31 | .nth(1) | ||
32 | .filter(|e| { | ||
33 | ast::String::can_cast(e.kind()) || ast::RawString::can_cast(e.kind()) | ||
34 | }) | ||
35 | } | ||
36 | _ => {} | ||
37 | } | ||
38 | } | ||
39 | } | ||
40 | pub(super) fn highlight_format_string( | ||
41 | &self, | ||
42 | range_stack: &mut HighlightedRangeStack, | ||
43 | string: &impl HasFormatSpecifier, | ||
44 | range: TextRange, | ||
45 | ) { | ||
46 | if self.format_string.as_ref() == Some(&SyntaxElement::from(string.syntax().clone())) { | ||
47 | range_stack.push(); | ||
48 | string.lex_format_specifier(|piece_range, kind| { | ||
49 | if let Some(highlight) = highlight_format_specifier(kind) { | ||
50 | range_stack.add(HighlightedRange { | ||
51 | range: piece_range + range.start(), | ||
52 | highlight: highlight.into(), | ||
53 | binding_hash: None, | ||
54 | }); | ||
55 | } | ||
56 | }); | ||
57 | range_stack.pop(); | ||
58 | } | ||
59 | } | ||
60 | } | ||
61 | |||
62 | fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HighlightTag> { | ||
63 | Some(match kind { | ||
64 | FormatSpecifier::Open | ||
65 | | FormatSpecifier::Close | ||
66 | | FormatSpecifier::Colon | ||
67 | | FormatSpecifier::Fill | ||
68 | | FormatSpecifier::Align | ||
69 | | FormatSpecifier::Sign | ||
70 | | FormatSpecifier::NumberSign | ||
71 | | FormatSpecifier::DollarSign | ||
72 | | FormatSpecifier::Dot | ||
73 | | FormatSpecifier::Asterisk | ||
74 | | FormatSpecifier::QuestionMark => HighlightTag::FormatSpecifier, | ||
75 | FormatSpecifier::Integer | FormatSpecifier::Zero => HighlightTag::NumericLiteral, | ||
76 | FormatSpecifier::Identifier => HighlightTag::Local, | ||
77 | }) | ||
78 | } | ||
diff --git a/crates/ide/src/syntax_highlighting/macro_rules.rs b/crates/ide/src/syntax_highlighting/macro_rules.rs new file mode 100644 index 000000000..4462af47e --- /dev/null +++ b/crates/ide/src/syntax_highlighting/macro_rules.rs | |||
@@ -0,0 +1,129 @@ | |||
1 | //! Syntax highlighting for macro_rules!. | ||
2 | use syntax::{SyntaxElement, SyntaxKind, SyntaxToken, TextRange, T}; | ||
3 | |||
4 | use crate::{HighlightTag, HighlightedRange}; | ||
5 | |||
6 | #[derive(Default)] | ||
7 | pub(super) struct MacroRulesHighlighter { | ||
8 | state: Option<MacroMatcherParseState>, | ||
9 | } | ||
10 | |||
11 | impl MacroRulesHighlighter { | ||
12 | pub(super) fn init(&mut self) { | ||
13 | self.state = Some(MacroMatcherParseState::default()); | ||
14 | } | ||
15 | |||
16 | pub(super) fn advance(&mut self, token: &SyntaxToken) { | ||
17 | if let Some(state) = self.state.as_mut() { | ||
18 | update_macro_rules_state(state, token); | ||
19 | } | ||
20 | } | ||
21 | |||
22 | pub(super) fn highlight(&self, element: SyntaxElement) -> Option<HighlightedRange> { | ||
23 | if let Some(state) = self.state.as_ref() { | ||
24 | if matches!(state.rule_state, RuleState::Matcher | RuleState::Expander) { | ||
25 | if let Some(range) = is_metavariable(element) { | ||
26 | return Some(HighlightedRange { | ||
27 | range, | ||
28 | highlight: HighlightTag::UnresolvedReference.into(), | ||
29 | binding_hash: None, | ||
30 | }); | ||
31 | } | ||
32 | } | ||
33 | } | ||
34 | None | ||
35 | } | ||
36 | } | ||
37 | |||
38 | struct MacroMatcherParseState { | ||
39 | /// Opening and corresponding closing bracket of the matcher or expander of the current rule | ||
40 | paren_ty: Option<(SyntaxKind, SyntaxKind)>, | ||
41 | paren_level: usize, | ||
42 | rule_state: RuleState, | ||
43 | /// Whether we are inside the outer `{` `}` macro block that holds the rules | ||
44 | in_invoc_body: bool, | ||
45 | } | ||
46 | |||
47 | impl Default for MacroMatcherParseState { | ||
48 | fn default() -> Self { | ||
49 | MacroMatcherParseState { | ||
50 | paren_ty: None, | ||
51 | paren_level: 0, | ||
52 | in_invoc_body: false, | ||
53 | rule_state: RuleState::None, | ||
54 | } | ||
55 | } | ||
56 | } | ||
57 | |||
58 | #[derive(Copy, Clone, Debug, PartialEq)] | ||
59 | enum RuleState { | ||
60 | Matcher, | ||
61 | Expander, | ||
62 | Between, | ||
63 | None, | ||
64 | } | ||
65 | |||
66 | impl RuleState { | ||
67 | fn transition(&mut self) { | ||
68 | *self = match self { | ||
69 | RuleState::Matcher => RuleState::Between, | ||
70 | RuleState::Expander => RuleState::None, | ||
71 | RuleState::Between => RuleState::Expander, | ||
72 | RuleState::None => RuleState::Matcher, | ||
73 | }; | ||
74 | } | ||
75 | } | ||
76 | |||
77 | fn update_macro_rules_state(state: &mut MacroMatcherParseState, tok: &SyntaxToken) { | ||
78 | if !state.in_invoc_body { | ||
79 | if tok.kind() == T!['{'] { | ||
80 | state.in_invoc_body = true; | ||
81 | } | ||
82 | return; | ||
83 | } | ||
84 | |||
85 | match state.paren_ty { | ||
86 | Some((open, close)) => { | ||
87 | if tok.kind() == open { | ||
88 | state.paren_level += 1; | ||
89 | } else if tok.kind() == close { | ||
90 | state.paren_level -= 1; | ||
91 | if state.paren_level == 0 { | ||
92 | state.rule_state.transition(); | ||
93 | state.paren_ty = None; | ||
94 | } | ||
95 | } | ||
96 | } | ||
97 | None => { | ||
98 | match tok.kind() { | ||
99 | T!['('] => { | ||
100 | state.paren_ty = Some((T!['('], T![')'])); | ||
101 | } | ||
102 | T!['{'] => { | ||
103 | state.paren_ty = Some((T!['{'], T!['}'])); | ||
104 | } | ||
105 | T!['['] => { | ||
106 | state.paren_ty = Some((T!['['], T![']'])); | ||
107 | } | ||
108 | _ => (), | ||
109 | } | ||
110 | if state.paren_ty.is_some() { | ||
111 | state.paren_level = 1; | ||
112 | state.rule_state.transition(); | ||
113 | } | ||
114 | } | ||
115 | } | ||
116 | } | ||
117 | |||
118 | fn is_metavariable(element: SyntaxElement) -> Option<TextRange> { | ||
119 | let tok = element.as_token()?; | ||
120 | match tok.kind() { | ||
121 | kind if kind == SyntaxKind::IDENT || kind.is_keyword() => { | ||
122 | if let Some(_dollar) = tok.prev_token().filter(|t| t.kind() == SyntaxKind::DOLLAR) { | ||
123 | return Some(tok.text_range()); | ||
124 | } | ||
125 | } | ||
126 | _ => (), | ||
127 | }; | ||
128 | None | ||
129 | } | ||