diff options
-rw-r--r-- | crates/assists/src/handlers/auto_import.rs | 55 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting.rs | 214 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting/format.rs | 78 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting/macro_rules.rs | 129 | ||||
-rw-r--r-- | crates/project_model/src/sysroot.rs | 4 | ||||
-rw-r--r-- | docs/dev/style.md | 32 | ||||
-rw-r--r-- | editors/code/src/inlay_hints.ts | 6 |
7 files changed, 326 insertions, 192 deletions
diff --git a/crates/assists/src/handlers/auto_import.rs b/crates/assists/src/handlers/auto_import.rs index e595b5b93..4a7059c83 100644 --- a/crates/assists/src/handlers/auto_import.rs +++ b/crates/assists/src/handlers/auto_import.rs | |||
@@ -6,6 +6,61 @@ use crate::{ | |||
6 | AssistContext, AssistId, AssistKind, Assists, GroupLabel, | 6 | AssistContext, AssistId, AssistKind, Assists, GroupLabel, |
7 | }; | 7 | }; |
8 | 8 | ||
9 | // Feature: Import Insertion | ||
10 | // | ||
11 | // Using the `auto-import` assist it is possible to insert missing imports for unresolved items. | ||
12 | // When inserting an import it will do so in a structured manner by keeping imports grouped, | ||
13 | // separated by a newline in the following order: | ||
14 | // | ||
15 | // - `std` and `core` | ||
16 | // - External Crates | ||
17 | // - Current Crate, paths prefixed by `crate` | ||
18 | // - Current Module, paths prefixed by `self` | ||
19 | // - Super Module, paths prefixed by `super` | ||
20 | // | ||
21 | // Example: | ||
22 | // ```rust | ||
23 | // use std::fs::File; | ||
24 | // | ||
25 | // use itertools::Itertools; | ||
26 | // use syntax::ast; | ||
27 | // | ||
28 | // use crate::utils::insert_use; | ||
29 | // | ||
30 | // use self::auto_import; | ||
31 | // | ||
32 | // use super::AssistContext; | ||
33 | // ``` | ||
34 | // | ||
35 | // .Merge Behaviour | ||
36 | // | ||
37 | // It is possible to configure how use-trees are merged with the `importMergeBehaviour` setting. | ||
38 | // It has the following configurations: | ||
39 | // | ||
40 | // - `full`: This setting will cause auto-import to always completely merge use-trees that share the | ||
41 | // same path prefix while also merging inner trees that share the same path-prefix. This kind of | ||
42 | // nesting is only supported in Rust versions later than 1.24. | ||
43 | // - `last`: This setting will cause auto-import to merge use-trees as long as the resulting tree | ||
44 | // will only contain a nesting of single segment paths at the very end. | ||
45 | // - `none`: This setting will cause auto-import to never merge use-trees keeping them as simple | ||
46 | // paths. | ||
47 | // | ||
48 | // In `VS Code` the configuration for this is `rust-analyzer.assist.importMergeBehaviour`. | ||
49 | // | ||
50 | // .Import Prefix | ||
51 | // | ||
52 | // The style of imports in the same crate is configurable through the `importPrefix` setting. | ||
53 | // It has the following configurations: | ||
54 | // | ||
55 | // - `by_crate`: This setting will force paths to be always absolute, starting with the `crate` | ||
56 | // prefix, unless the item is defined outside of the current crate. | ||
57 | // - `by_self`: This setting will force paths that are relative to the current module to always | ||
58 | // start with `self`. This will result in paths that always start with either `crate`, `self`, | ||
59 | // `super` or an extern crate identifier. | ||
60 | // - `plain`: This setting does not impose any restrictions in imports. | ||
61 | // | ||
62 | // In `VS Code` the configuration for this is `rust-analyzer.assist.importPrefix`. | ||
63 | |||
9 | // Assist: auto_import | 64 | // Assist: auto_import |
10 | // | 65 | // |
11 | // If the name is unresolved, provides all possible imports for it. | 66 | // If the name is unresolved, provides all possible imports for it. |
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs index 6aafd6fd5..527888306 100644 --- a/crates/ide/src/syntax_highlighting.rs +++ b/crates/ide/src/syntax_highlighting.rs | |||
@@ -1,6 +1,8 @@ | |||
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 | ||
@@ -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()?; |
@@ -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 | } | ||
diff --git a/crates/project_model/src/sysroot.rs b/crates/project_model/src/sysroot.rs index e529e07b0..b2ff98a15 100644 --- a/crates/project_model/src/sysroot.rs +++ b/crates/project_model/src/sysroot.rs | |||
@@ -49,6 +49,7 @@ impl Sysroot { | |||
49 | } | 49 | } |
50 | 50 | ||
51 | pub fn discover(cargo_toml: &AbsPath) -> Result<Sysroot> { | 51 | pub fn discover(cargo_toml: &AbsPath) -> Result<Sysroot> { |
52 | log::debug!("Discovering sysroot for {}", cargo_toml.display()); | ||
52 | let current_dir = cargo_toml.parent().unwrap(); | 53 | let current_dir = cargo_toml.parent().unwrap(); |
53 | let sysroot_src_dir = discover_sysroot_src_dir(current_dir)?; | 54 | let sysroot_src_dir = discover_sysroot_src_dir(current_dir)?; |
54 | let res = Sysroot::load(&sysroot_src_dir)?; | 55 | let res = Sysroot::load(&sysroot_src_dir)?; |
@@ -115,12 +116,14 @@ fn discover_sysroot_src_dir(current_dir: &AbsPath) -> Result<AbsPathBuf> { | |||
115 | if let Ok(path) = env::var("RUST_SRC_PATH") { | 116 | if let Ok(path) = env::var("RUST_SRC_PATH") { |
116 | let path = AbsPathBuf::try_from(path.as_str()) | 117 | let path = AbsPathBuf::try_from(path.as_str()) |
117 | .map_err(|path| format_err!("RUST_SRC_PATH must be absolute: {}", path.display()))?; | 118 | .map_err(|path| format_err!("RUST_SRC_PATH must be absolute: {}", path.display()))?; |
119 | log::debug!("Discovered sysroot by RUST_SRC_PATH: {}", path.display()); | ||
118 | return Ok(path); | 120 | return Ok(path); |
119 | } | 121 | } |
120 | 122 | ||
121 | let sysroot_path = { | 123 | let sysroot_path = { |
122 | let mut rustc = Command::new(toolchain::rustc()); | 124 | let mut rustc = Command::new(toolchain::rustc()); |
123 | rustc.current_dir(current_dir).args(&["--print", "sysroot"]); | 125 | rustc.current_dir(current_dir).args(&["--print", "sysroot"]); |
126 | log::debug!("Discovering sysroot by {:?}", rustc); | ||
124 | let stdout = utf8_stdout(rustc)?; | 127 | let stdout = utf8_stdout(rustc)?; |
125 | AbsPathBuf::assert(PathBuf::from(stdout)) | 128 | AbsPathBuf::assert(PathBuf::from(stdout)) |
126 | }; | 129 | }; |
@@ -150,6 +153,7 @@ fn get_rust_src(sysroot_path: &AbsPath) -> Option<AbsPathBuf> { | |||
150 | // FIXME: remove `src` when 1.47 comes out | 153 | // FIXME: remove `src` when 1.47 comes out |
151 | // https://github.com/rust-lang/rust/pull/73265 | 154 | // https://github.com/rust-lang/rust/pull/73265 |
152 | let rust_src = sysroot_path.join("lib/rustlib/src/rust"); | 155 | let rust_src = sysroot_path.join("lib/rustlib/src/rust"); |
156 | log::debug!("Checking sysroot (looking for `library` and `src` dirs): {}", rust_src.display()); | ||
153 | ["library", "src"].iter().map(|it| rust_src.join(it)).find(|it| it.exists()) | 157 | ["library", "src"].iter().map(|it| rust_src.join(it)).find(|it| it.exists()) |
154 | } | 158 | } |
155 | 159 | ||
diff --git a/docs/dev/style.md b/docs/dev/style.md index 59067d234..20f1b6253 100644 --- a/docs/dev/style.md +++ b/docs/dev/style.md | |||
@@ -186,6 +186,31 @@ impl Person { | |||
186 | } | 186 | } |
187 | ``` | 187 | ``` |
188 | 188 | ||
189 | ## Constructors | ||
190 | |||
191 | Prefer `Default` to zero-argument `new` function | ||
192 | |||
193 | ```rust | ||
194 | // Good | ||
195 | #[derive(Default)] | ||
196 | struct Foo { | ||
197 | bar: Option<Bar> | ||
198 | } | ||
199 | |||
200 | // Not as good | ||
201 | struct Foo { | ||
202 | bar: Option<Bar> | ||
203 | } | ||
204 | |||
205 | impl Foo { | ||
206 | fn new() -> Foo { | ||
207 | Foo { bar: None } | ||
208 | } | ||
209 | } | ||
210 | ``` | ||
211 | |||
212 | Prefer `Default` even it has to be implemented manually. | ||
213 | |||
189 | ## Avoid Monomorphization | 214 | ## Avoid Monomorphization |
190 | 215 | ||
191 | Rust uses monomorphization to compile generic code, meaning that for each instantiation of a generic functions with concrete types, the function is compiled afresh, *per crate*. | 216 | Rust uses monomorphization to compile generic code, meaning that for each instantiation of a generic functions with concrete types, the function is compiled afresh, *per crate*. |
@@ -223,6 +248,8 @@ fn frbonicate(f: impl AsRef<Path>) { | |||
223 | 248 | ||
224 | # Premature Pessimization | 249 | # Premature Pessimization |
225 | 250 | ||
251 | ## Avoid Allocations | ||
252 | |||
226 | Avoid writing code which is slower than it needs to be. | 253 | Avoid writing code which is slower than it needs to be. |
227 | Don't allocate a `Vec` where an iterator would do, don't allocate strings needlessly. | 254 | Don't allocate a `Vec` where an iterator would do, don't allocate strings needlessly. |
228 | 255 | ||
@@ -242,6 +269,8 @@ if words.len() != 2 { | |||
242 | } | 269 | } |
243 | ``` | 270 | ``` |
244 | 271 | ||
272 | ## Push Allocations to the Call Site | ||
273 | |||
245 | If allocation is inevitable, let the caller allocate the resource: | 274 | If allocation is inevitable, let the caller allocate the resource: |
246 | 275 | ||
247 | ```rust | 276 | ```rust |
@@ -257,6 +286,9 @@ fn frobnicate(s: &str) { | |||
257 | } | 286 | } |
258 | ``` | 287 | ``` |
259 | 288 | ||
289 | This is better because it reveals the costs. | ||
290 | It is also more efficient when the caller already owns the allocation. | ||
291 | |||
260 | ## Collection types | 292 | ## Collection types |
261 | 293 | ||
262 | Prefer `rustc_hash::FxHashMap` and `rustc_hash::FxHashSet` instead of the ones in `std::collections`. | 294 | Prefer `rustc_hash::FxHashMap` and `rustc_hash::FxHashSet` instead of the ones in `std::collections`. |
diff --git a/editors/code/src/inlay_hints.ts b/editors/code/src/inlay_hints.ts index 30ade9b98..ed0db2924 100644 --- a/editors/code/src/inlay_hints.ts +++ b/editors/code/src/inlay_hints.ts | |||
@@ -44,10 +44,12 @@ const paramHints = createHintStyle("parameter"); | |||
44 | const chainingHints = createHintStyle("chaining"); | 44 | const chainingHints = createHintStyle("chaining"); |
45 | 45 | ||
46 | function createHintStyle(hintKind: "type" | "parameter" | "chaining") { | 46 | function createHintStyle(hintKind: "type" | "parameter" | "chaining") { |
47 | // U+200C is a zero-width non-joiner to prevent the editor from forming a ligature | ||
48 | // between code and type hints | ||
47 | const [pos, render] = ({ | 49 | const [pos, render] = ({ |
48 | type: ["after", (label: string) => `: ${label}`], | 50 | type: ["after", (label: string) => `\u{200c}: ${label}`], |
49 | parameter: ["before", (label: string) => `${label}: `], | 51 | parameter: ["before", (label: string) => `${label}: `], |
50 | chaining: ["after", (label: string) => `: ${label}`], | 52 | chaining: ["after", (label: string) => `\u{200c}: ${label}`], |
51 | } as const)[hintKind]; | 53 | } as const)[hintKind]; |
52 | 54 | ||
53 | const fg = new vscode.ThemeColor(`rust_analyzer.inlayHints.foreground.${hintKind}Hints`); | 55 | const fg = new vscode.ThemeColor(`rust_analyzer.inlayHints.foreground.${hintKind}Hints`); |