aboutsummaryrefslogtreecommitdiff
path: root/crates/ide
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide')
-rw-r--r--crates/ide/src/doc_links.rs (renamed from crates/ide/src/link_rewrite.rs)285
-rw-r--r--crates/ide/src/hover.rs2
-rw-r--r--crates/ide/src/inlay_hints.rs77
-rw-r--r--crates/ide/src/lib.rs24
-rw-r--r--crates/ide/src/prime_caches.rs43
-rw-r--r--crates/ide/src/references.rs1
-rw-r--r--crates/ide/src/references/rename.rs175
-rw-r--r--crates/ide/src/syntax_highlighting.rs118
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_strings.html2
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlighting.html6
-rw-r--r--crates/ide/src/syntax_highlighting/tests.rs4
11 files changed, 639 insertions, 98 deletions
diff --git a/crates/ide/src/link_rewrite.rs b/crates/ide/src/doc_links.rs
index c317a2379..06af36b73 100644
--- a/crates/ide/src/link_rewrite.rs
+++ b/crates/ide/src/doc_links.rs
@@ -1,13 +1,27 @@
1//! Resolves and rewrites links in markdown documentation. 1//! Resolves and rewrites links in markdown documentation.
2//!
3//! Most of the implementation can be found in [`hir::doc_links`].
4 2
5use hir::{Adt, Crate, HasAttrs, ModuleDef}; 3use std::iter::once;
6use ide_db::{defs::Definition, RootDatabase}; 4
5use itertools::Itertools;
7use pulldown_cmark::{CowStr, Event, LinkType, Options, Parser, Tag}; 6use pulldown_cmark::{CowStr, Event, LinkType, Options, Parser, Tag};
8use pulldown_cmark_to_cmark::{cmark_with_options, Options as CmarkOptions}; 7use pulldown_cmark_to_cmark::{cmark_with_options, Options as CmarkOptions};
9use url::Url; 8use url::Url;
10 9
10use hir::{
11 db::{DefDatabase, HirDatabase},
12 Adt, AsAssocItem, AsName, AssocItem, AssocItemContainer, Crate, Field, HasAttrs, ItemInNs,
13 ModuleDef,
14};
15use ide_db::{
16 defs::{classify_name, classify_name_ref, Definition},
17 RootDatabase,
18};
19use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset, T};
20
21use crate::{FilePosition, Semantics};
22
23pub type DocumentationLink = String;
24
11/// Rewrite documentation links in markdown to point to an online host (e.g. docs.rs) 25/// Rewrite documentation links in markdown to point to an online host (e.g. docs.rs)
12pub fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> String { 26pub fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> String {
13 let doc = Parser::new_with_broken_link_callback( 27 let doc = Parser::new_with_broken_link_callback(
@@ -80,6 +94,70 @@ pub fn remove_links(markdown: &str) -> String {
80 out 94 out
81} 95}
82 96
97// FIXME:
98// BUG: For Option::Some
99// Returns https://doc.rust-lang.org/nightly/core/prelude/v1/enum.Option.html#variant.Some
100// Instead of https://doc.rust-lang.org/nightly/core/option/enum.Option.html
101//
102// This should cease to be a problem if RFC2988 (Stable Rustdoc URLs) is implemented
103// https://github.com/rust-lang/rfcs/pull/2988
104fn get_doc_link(db: &RootDatabase, definition: Definition) -> Option<String> {
105 // Get the outermost definition for the moduledef. This is used to resolve the public path to the type,
106 // then we can join the method, field, etc onto it if required.
107 let target_def: ModuleDef = match definition {
108 Definition::ModuleDef(moddef) => match moddef {
109 ModuleDef::Function(f) => f
110 .as_assoc_item(db)
111 .and_then(|assoc| match assoc.container(db) {
112 AssocItemContainer::Trait(t) => Some(t.into()),
113 AssocItemContainer::ImplDef(impld) => {
114 impld.target_ty(db).as_adt().map(|adt| adt.into())
115 }
116 })
117 .unwrap_or_else(|| f.clone().into()),
118 moddef => moddef,
119 },
120 Definition::Field(f) => f.parent_def(db).into(),
121 // FIXME: Handle macros
122 _ => return None,
123 };
124
125 let ns = ItemInNs::from(target_def.clone());
126
127 let module = definition.module(db)?;
128 let krate = module.krate();
129 let import_map = db.import_map(krate.into());
130 let base = once(krate.declaration_name(db)?.to_string())
131 .chain(import_map.path_of(ns)?.segments.iter().map(|name| name.to_string()))
132 .join("/");
133
134 let filename = get_symbol_filename(db, &target_def);
135 let fragment = match definition {
136 Definition::ModuleDef(moddef) => match moddef {
137 ModuleDef::Function(f) => {
138 get_symbol_fragment(db, &FieldOrAssocItem::AssocItem(AssocItem::Function(f)))
139 }
140 ModuleDef::Const(c) => {
141 get_symbol_fragment(db, &FieldOrAssocItem::AssocItem(AssocItem::Const(c)))
142 }
143 ModuleDef::TypeAlias(ty) => {
144 get_symbol_fragment(db, &FieldOrAssocItem::AssocItem(AssocItem::TypeAlias(ty)))
145 }
146 _ => None,
147 },
148 Definition::Field(field) => get_symbol_fragment(db, &FieldOrAssocItem::Field(field)),
149 _ => None,
150 };
151
152 get_doc_url(db, &krate)
153 .and_then(|url| url.join(&base).ok())
154 .and_then(|url| filename.as_deref().and_then(|f| url.join(f).ok()))
155 .and_then(
156 |url| if let Some(fragment) = fragment { url.join(&fragment).ok() } else { Some(url) },
157 )
158 .map(|url| url.into_string())
159}
160
83fn rewrite_intra_doc_link( 161fn rewrite_intra_doc_link(
84 db: &RootDatabase, 162 db: &RootDatabase,
85 def: Definition, 163 def: Definition,
@@ -138,7 +216,29 @@ fn rewrite_url_link(db: &RootDatabase, def: ModuleDef, target: &str) -> Option<S
138 .map(|url| url.into_string()) 216 .map(|url| url.into_string())
139} 217}
140 218
141// Rewrites a markdown document, resolving links using `callback` and additionally striping prefixes/suffixes on link titles. 219/// Retrieve a link to documentation for the given symbol.
220pub(crate) fn external_docs(
221 db: &RootDatabase,
222 position: &FilePosition,
223) -> Option<DocumentationLink> {
224 let sema = Semantics::new(db);
225 let file = sema.parse(position.file_id).syntax().clone();
226 let token = pick_best(file.token_at_offset(position.offset))?;
227 let token = sema.descend_into_macros(token);
228
229 let node = token.parent();
230 let definition = match_ast! {
231 match node {
232 ast::NameRef(name_ref) => classify_name_ref(&sema, &name_ref).map(|d| d.definition(sema.db)),
233 ast::Name(name) => classify_name(&sema, &name).map(|d| d.definition(sema.db)),
234 _ => None,
235 }
236 };
237
238 get_doc_link(db, definition?)
239}
240
241/// Rewrites a markdown document, applying 'callback' to each link.
142fn map_links<'e>( 242fn map_links<'e>(
143 events: impl Iterator<Item = Event<'e>>, 243 events: impl Iterator<Item = Event<'e>>,
144 callback: impl Fn(&str, &str) -> (String, String), 244 callback: impl Fn(&str, &str) -> (String, String),
@@ -239,6 +339,12 @@ fn ns_from_intra_spec(s: &str) -> Option<hir::Namespace> {
239 .next() 339 .next()
240} 340}
241 341
342/// Get the root URL for the documentation of a crate.
343///
344/// ```
345/// https://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.next
346/// ^^^^^^^^^^^^^^^^^^^^^^^^^^
347/// ```
242fn get_doc_url(db: &RootDatabase, krate: &Crate) -> Option<Url> { 348fn get_doc_url(db: &RootDatabase, krate: &Crate) -> Option<Url> {
243 krate 349 krate
244 .get_html_root_url(db) 350 .get_html_root_url(db)
@@ -255,8 +361,11 @@ fn get_doc_url(db: &RootDatabase, krate: &Crate) -> Option<Url> {
255 361
256/// Get the filename and extension generated for a symbol by rustdoc. 362/// Get the filename and extension generated for a symbol by rustdoc.
257/// 363///
258/// Example: `struct.Shard.html` 364/// ```
259fn get_symbol_filename(db: &RootDatabase, definition: &ModuleDef) -> Option<String> { 365/// https://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.next
366/// ^^^^^^^^^^^^^^^^^^^
367/// ```
368fn get_symbol_filename(db: &dyn HirDatabase, definition: &ModuleDef) -> Option<String> {
260 Some(match definition { 369 Some(match definition {
261 ModuleDef::Adt(adt) => match adt { 370 ModuleDef::Adt(adt) => match adt {
262 Adt::Struct(s) => format!("struct.{}.html", s.name(db)), 371 Adt::Struct(s) => format!("struct.{}.html", s.name(db)),
@@ -266,7 +375,7 @@ fn get_symbol_filename(db: &RootDatabase, definition: &ModuleDef) -> Option<Stri
266 ModuleDef::Module(_) => "index.html".to_string(), 375 ModuleDef::Module(_) => "index.html".to_string(),
267 ModuleDef::Trait(t) => format!("trait.{}.html", t.name(db)), 376 ModuleDef::Trait(t) => format!("trait.{}.html", t.name(db)),
268 ModuleDef::TypeAlias(t) => format!("type.{}.html", t.name(db)), 377 ModuleDef::TypeAlias(t) => format!("type.{}.html", t.name(db)),
269 ModuleDef::BuiltinType(t) => format!("primitive.{}.html", t), 378 ModuleDef::BuiltinType(t) => format!("primitive.{}.html", t.as_name()),
270 ModuleDef::Function(f) => format!("fn.{}.html", f.name(db)), 379 ModuleDef::Function(f) => format!("fn.{}.html", f.name(db)),
271 ModuleDef::EnumVariant(ev) => { 380 ModuleDef::EnumVariant(ev) => {
272 format!("enum.{}.html#variant.{}", ev.parent_enum(db).name(db), ev.name(db)) 381 format!("enum.{}.html#variant.{}", ev.parent_enum(db).name(db), ev.name(db))
@@ -275,3 +384,163 @@ fn get_symbol_filename(db: &RootDatabase, definition: &ModuleDef) -> Option<Stri
275 ModuleDef::Static(s) => format!("static.{}.html", s.name(db)?), 384 ModuleDef::Static(s) => format!("static.{}.html", s.name(db)?),
276 }) 385 })
277} 386}
387
388enum FieldOrAssocItem {
389 Field(Field),
390 AssocItem(AssocItem),
391}
392
393/// Get the fragment required to link to a specific field, method, associated type, or associated constant.
394///
395/// ```
396/// https://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.next
397/// ^^^^^^^^^^^^^^
398/// ```
399fn get_symbol_fragment(db: &dyn HirDatabase, field_or_assoc: &FieldOrAssocItem) -> Option<String> {
400 Some(match field_or_assoc {
401 FieldOrAssocItem::Field(field) => format!("#structfield.{}", field.name(db)),
402 FieldOrAssocItem::AssocItem(assoc) => match assoc {
403 AssocItem::Function(function) => {
404 let is_trait_method = matches!(
405 function.as_assoc_item(db).map(|assoc| assoc.container(db)),
406 Some(AssocItemContainer::Trait(..))
407 );
408 // This distinction may get more complicated when specialisation is available.
409 // Rustdoc makes this decision based on whether a method 'has defaultness'.
410 // Currently this is only the case for provided trait methods.
411 if is_trait_method && !function.has_body(db) {
412 format!("#tymethod.{}", function.name(db))
413 } else {
414 format!("#method.{}", function.name(db))
415 }
416 }
417 AssocItem::Const(constant) => format!("#associatedconstant.{}", constant.name(db)?),
418 AssocItem::TypeAlias(ty) => format!("#associatedtype.{}", ty.name(db)),
419 },
420 })
421}
422
423fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
424 return tokens.max_by_key(priority);
425 fn priority(n: &SyntaxToken) -> usize {
426 match n.kind() {
427 IDENT | INT_NUMBER => 3,
428 T!['('] | T![')'] => 2,
429 kind if kind.is_trivia() => 0,
430 _ => 1,
431 }
432 }
433}
434
435#[cfg(test)]
436mod tests {
437 use expect_test::{expect, Expect};
438
439 use crate::fixture;
440
441 fn check(ra_fixture: &str, expect: Expect) {
442 let (analysis, position) = fixture::position(ra_fixture);
443 let url = analysis.external_docs(position).unwrap().expect("could not find url for symbol");
444
445 expect.assert_eq(&url)
446 }
447
448 #[test]
449 fn test_doc_url_struct() {
450 check(
451 r#"
452pub struct Fo<|>o;
453"#,
454 expect![[r#"https://docs.rs/test/*/test/struct.Foo.html"#]],
455 );
456 }
457
458 #[test]
459 fn test_doc_url_fn() {
460 check(
461 r#"
462pub fn fo<|>o() {}
463"#,
464 expect![[r##"https://docs.rs/test/*/test/fn.foo.html#method.foo"##]],
465 );
466 }
467
468 #[test]
469 fn test_doc_url_inherent_method() {
470 check(
471 r#"
472pub struct Foo;
473
474impl Foo {
475 pub fn met<|>hod() {}
476}
477
478"#,
479 expect![[r##"https://docs.rs/test/*/test/struct.Foo.html#method.method"##]],
480 );
481 }
482
483 #[test]
484 fn test_doc_url_trait_provided_method() {
485 check(
486 r#"
487pub trait Bar {
488 fn met<|>hod() {}
489}
490
491"#,
492 expect![[r##"https://docs.rs/test/*/test/trait.Bar.html#method.method"##]],
493 );
494 }
495
496 #[test]
497 fn test_doc_url_trait_required_method() {
498 check(
499 r#"
500pub trait Foo {
501 fn met<|>hod();
502}
503
504"#,
505 expect![[r##"https://docs.rs/test/*/test/trait.Foo.html#tymethod.method"##]],
506 );
507 }
508
509 #[test]
510 fn test_doc_url_field() {
511 check(
512 r#"
513pub struct Foo {
514 pub fie<|>ld: ()
515}
516
517"#,
518 expect![[r##"https://docs.rs/test/*/test/struct.Foo.html#structfield.field"##]],
519 );
520 }
521
522 // FIXME: ImportMap will return re-export paths instead of public module
523 // paths. The correct path to documentation will never be a re-export.
524 // This problem stops us from resolving stdlib items included in the prelude
525 // such as `Option::Some` correctly.
526 #[ignore = "ImportMap may return re-exports"]
527 #[test]
528 fn test_reexport_order() {
529 check(
530 r#"
531pub mod wrapper {
532 pub use module::Item;
533
534 pub mod module {
535 pub struct Item;
536 }
537}
538
539fn foo() {
540 let bar: wrapper::It<|>em;
541}
542 "#,
543 expect![[r#"https://docs.rs/test/*/test/wrapper/module/struct.Item.html"#]],
544 )
545 }
546}
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index 53265488e..6290b35bd 100644
--- a/crates/ide/src/hover.rs
+++ b/crates/ide/src/hover.rs
@@ -14,7 +14,7 @@ use test_utils::mark;
14 14
15use crate::{ 15use crate::{
16 display::{macro_label, ShortLabel, ToNav, TryToNav}, 16 display::{macro_label, ShortLabel, ToNav, TryToNav},
17 link_rewrite::{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, 20 runnables::runnable,
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs
index 7d716577e..2ed84095d 100644
--- a/crates/ide/src/inlay_hints.rs
+++ b/crates/ide/src/inlay_hints.rs
@@ -231,12 +231,17 @@ fn hint_iterator(
231 const LABEL_START: &str = "impl Iterator<Item = "; 231 const LABEL_START: &str = "impl Iterator<Item = ";
232 const LABEL_END: &str = ">"; 232 const LABEL_END: &str = ">";
233 233
234 let ty_display = ty.display_truncated( 234 let ty_display = hint_iterator(sema, config, &ty)
235 db, 235 .map(|assoc_type_impl| assoc_type_impl.to_string())
236 config 236 .unwrap_or_else(|| {
237 .max_length 237 ty.display_truncated(
238 .map(|len| len.saturating_sub(LABEL_START.len() + LABEL_END.len())), 238 db,
239 ); 239 config
240 .max_length
241 .map(|len| len.saturating_sub(LABEL_START.len() + LABEL_END.len())),
242 )
243 .to_string()
244 });
240 return Some(format!("{}{}{}", LABEL_START, ty_display, LABEL_END).into()); 245 return Some(format!("{}{}{}", LABEL_START, ty_display, LABEL_END).into());
241 } 246 }
242 } 247 }
@@ -1002,18 +1007,6 @@ fn main() {
1002 1007
1003 println!("Unit expr"); 1008 println!("Unit expr");
1004} 1009}
1005
1006//- /alloc.rs crate:alloc deps:core
1007mod collections {
1008 struct Vec<T> {}
1009 impl<T> Vec<T> {
1010 fn new() -> Self { Vec {} }
1011 fn push(&mut self, t: T) { }
1012 }
1013 impl<T> IntoIterator for Vec<T> {
1014 type Item=T;
1015 }
1016}
1017"#, 1010"#,
1018 ); 1011 );
1019 } 1012 }
@@ -1043,17 +1036,6 @@ fn main() {
1043 //^ &str 1036 //^ &str
1044 } 1037 }
1045} 1038}
1046//- /alloc.rs crate:alloc deps:core
1047mod collections {
1048 struct Vec<T> {}
1049 impl<T> Vec<T> {
1050 fn new() -> Self { Vec {} }
1051 fn push(&mut self, t: T) { }
1052 }
1053 impl<T> IntoIterator for Vec<T> {
1054 type Item=T;
1055 }
1056}
1057"#, 1039"#,
1058 ); 1040 );
1059 } 1041 }
@@ -1183,4 +1165,41 @@ fn main() {
1183 "#]], 1165 "#]],
1184 ); 1166 );
1185 } 1167 }
1168
1169 #[test]
1170 fn shorten_iterators_in_associated_params() {
1171 check_with_config(
1172 InlayHintsConfig {
1173 parameter_hints: false,
1174 type_hints: true,
1175 chaining_hints: false,
1176 max_length: None,
1177 },
1178 r#"
1179use core::iter;
1180
1181pub struct SomeIter<T> {}
1182
1183impl<T> SomeIter<T> {
1184 pub fn new() -> Self { SomeIter {} }
1185 pub fn push(&mut self, t: T) {}
1186}
1187
1188impl<T> Iterator for SomeIter<T> {
1189 type Item = T;
1190 fn next(&mut self) -> Option<Self::Item> {
1191 None
1192 }
1193}
1194
1195fn main() {
1196 let mut some_iter = SomeIter::new();
1197 //^^^^^^^^^^^^^ SomeIter<Take<Repeat<i32>>>
1198 some_iter.push(iter::repeat(2).take(2));
1199 let iter_of_iters = some_iter.take(2);
1200 //^^^^^^^^^^^^^ impl Iterator<Item = impl Iterator<Item = i32>>
1201}
1202"#,
1203 );
1204 }
1186} 1205}
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index 57f3581b6..aaf9b3b4b 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -45,8 +45,8 @@ mod status;
45mod syntax_highlighting; 45mod syntax_highlighting;
46mod syntax_tree; 46mod syntax_tree;
47mod typing; 47mod typing;
48mod link_rewrite;
49mod markdown_remove; 48mod markdown_remove;
49mod doc_links;
50 50
51use std::sync::Arc; 51use std::sync::Arc;
52 52
@@ -77,7 +77,10 @@ pub use crate::{
77 hover::{HoverAction, HoverConfig, HoverGotoTypeData, HoverResult}, 77 hover::{HoverAction, HoverConfig, HoverGotoTypeData, HoverResult},
78 inlay_hints::{InlayHint, InlayHintsConfig, InlayKind}, 78 inlay_hints::{InlayHint, InlayHintsConfig, InlayKind},
79 markup::Markup, 79 markup::Markup,
80 references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult}, 80 prime_caches::PrimeCachesProgress,
81 references::{
82 Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult, RenameError,
83 },
81 runnables::{Runnable, RunnableKind, TestId}, 84 runnables::{Runnable, RunnableKind, TestId},
82 syntax_highlighting::{ 85 syntax_highlighting::{
83 Highlight, HighlightModifier, HighlightModifiers, HighlightTag, HighlightedRange, 86 Highlight, HighlightModifier, HighlightModifiers, HighlightTag, HighlightedRange,
@@ -221,8 +224,11 @@ impl Analysis {
221 self.with_db(|db| status::status(&*db, file_id)) 224 self.with_db(|db| status::status(&*db, file_id))
222 } 225 }
223 226
224 pub fn prime_caches(&self, files: Vec<FileId>) -> Cancelable<()> { 227 pub fn prime_caches<F>(&self, cb: F) -> Cancelable<()>
225 self.with_db(|db| prime_caches::prime_caches(db, files)) 228 where
229 F: Fn(PrimeCachesProgress) + Sync + std::panic::UnwindSafe,
230 {
231 self.with_db(move |db| prime_caches::prime_caches(db, &cb))
226 } 232 }
227 233
228 /// Gets the text of the source file. 234 /// Gets the text of the source file.
@@ -382,6 +388,14 @@ impl Analysis {
382 self.with_db(|db| hover::hover(db, position, links_in_hover, markdown)) 388 self.with_db(|db| hover::hover(db, position, links_in_hover, markdown))
383 } 389 }
384 390
391 /// Return URL(s) for the documentation of the symbol under the cursor.
392 pub fn external_docs(
393 &self,
394 position: FilePosition,
395 ) -> Cancelable<Option<doc_links::DocumentationLink>> {
396 self.with_db(|db| doc_links::external_docs(db, &position))
397 }
398
385 /// Computes parameter information for the given call expression. 399 /// Computes parameter information for the given call expression.
386 pub fn call_info(&self, position: FilePosition) -> Cancelable<Option<CallInfo>> { 400 pub fn call_info(&self, position: FilePosition) -> Cancelable<Option<CallInfo>> {
387 self.with_db(|db| call_info::call_info(db, position)) 401 self.with_db(|db| call_info::call_info(db, position))
@@ -490,7 +504,7 @@ impl Analysis {
490 &self, 504 &self,
491 position: FilePosition, 505 position: FilePosition,
492 new_name: &str, 506 new_name: &str,
493 ) -> Cancelable<Option<RangeInfo<SourceChange>>> { 507 ) -> Cancelable<Result<RangeInfo<SourceChange>, RenameError>> {
494 self.with_db(|db| references::rename(db, position, new_name)) 508 self.with_db(|db| references::rename(db, position, new_name))
495 } 509 }
496 510
diff --git a/crates/ide/src/prime_caches.rs b/crates/ide/src/prime_caches.rs
index c5ab5a1d8..9687c2734 100644
--- a/crates/ide/src/prime_caches.rs
+++ b/crates/ide/src/prime_caches.rs
@@ -3,10 +3,45 @@
3//! request takes longer to compute. This modules implemented prepopulating of 3//! request takes longer to compute. This modules implemented prepopulating of
4//! various caches, it's not really advanced at the moment. 4//! various caches, it's not really advanced at the moment.
5 5
6use crate::{FileId, RootDatabase}; 6use base_db::SourceDatabase;
7use hir::db::DefDatabase;
7 8
8pub(crate) fn prime_caches(db: &RootDatabase, files: Vec<FileId>) { 9use crate::RootDatabase;
9 for file in files { 10
10 let _ = crate::syntax_highlighting::highlight(db, file, None, false); 11#[derive(Debug)]
12pub enum PrimeCachesProgress {
13 Started,
14 /// We started indexing a crate.
15 StartedOnCrate {
16 on_crate: String,
17 n_done: usize,
18 n_total: usize,
19 },
20 /// We finished indexing all crates.
21 Finished,
22}
23
24pub(crate) fn prime_caches(db: &RootDatabase, cb: &(dyn Fn(PrimeCachesProgress) + Sync)) {
25 let _p = profile::span("prime_caches");
26 let graph = db.crate_graph();
27 let topo = &graph.crates_in_topological_order();
28
29 cb(PrimeCachesProgress::Started);
30
31 // FIXME: This would be easy to parallelize, since it's in the ideal ordering for that.
32 // Unfortunately rayon prevents panics from propagation out of a `scope`, which breaks
33 // cancellation, so we cannot use rayon.
34 for (i, krate) in topo.iter().enumerate() {
35 let crate_name =
36 graph[*krate].declaration_name.as_ref().map(ToString::to_string).unwrap_or_default();
37
38 cb(PrimeCachesProgress::StartedOnCrate {
39 on_crate: crate_name,
40 n_done: i,
41 n_total: topo.len(),
42 });
43 db.crate_def_map(*krate);
11 } 44 }
45
46 cb(PrimeCachesProgress::Finished);
12} 47}
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs
index 9315f7354..f65a05ea3 100644
--- a/crates/ide/src/references.rs
+++ b/crates/ide/src/references.rs
@@ -26,6 +26,7 @@ use syntax::{
26use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeInfo}; 26use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeInfo};
27 27
28pub(crate) use self::rename::rename; 28pub(crate) use self::rename::rename;
29pub use self::rename::RenameError;
29 30
30pub use ide_db::search::{Reference, ReferenceAccess, ReferenceKind}; 31pub use ide_db::search::{Reference, ReferenceAccess, ReferenceKind};
31 32
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs
index 8cbe1ae5a..f3b5cfc8c 100644
--- a/crates/ide/src/references/rename.rs
+++ b/crates/ide/src/references/rename.rs
@@ -6,11 +6,16 @@ use ide_db::{
6 defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass}, 6 defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass},
7 RootDatabase, 7 RootDatabase,
8}; 8};
9use std::convert::TryInto; 9
10use std::{
11 convert::TryInto,
12 error::Error,
13 fmt::{self, Display},
14};
10use syntax::{ 15use syntax::{
11 algo::find_node_at_offset, 16 algo::find_node_at_offset,
12 ast::{self, NameOwner}, 17 ast::{self, NameOwner},
13 lex_single_valid_syntax_kind, match_ast, AstNode, SyntaxKind, SyntaxNode, SyntaxToken, 18 lex_single_syntax_kind, match_ast, AstNode, SyntaxKind, SyntaxNode, SyntaxToken,
14}; 19};
15use test_utils::mark; 20use test_utils::mark;
16use text_edit::TextEdit; 21use text_edit::TextEdit;
@@ -20,17 +25,37 @@ use crate::{
20 SourceChange, SourceFileEdit, TextRange, TextSize, 25 SourceChange, SourceFileEdit, TextRange, TextSize,
21}; 26};
22 27
28#[derive(Debug)]
29pub struct RenameError(pub(crate) String);
30
31impl fmt::Display for RenameError {
32 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33 Display::fmt(&self.0, f)
34 }
35}
36
37impl Error for RenameError {}
38
23pub(crate) fn rename( 39pub(crate) fn rename(
24 db: &RootDatabase, 40 db: &RootDatabase,
25 position: FilePosition, 41 position: FilePosition,
26 new_name: &str, 42 new_name: &str,
27) -> Option<RangeInfo<SourceChange>> { 43) -> Result<RangeInfo<SourceChange>, RenameError> {
28 let sema = Semantics::new(db); 44 let sema = Semantics::new(db);
29 45
30 match lex_single_valid_syntax_kind(new_name)? { 46 match lex_single_syntax_kind(new_name) {
31 SyntaxKind::IDENT | SyntaxKind::UNDERSCORE => (), 47 Some(res) => match res {
32 SyntaxKind::SELF_KW => return rename_to_self(&sema, position), 48 (SyntaxKind::IDENT, _) => (),
33 _ => return None, 49 (SyntaxKind::UNDERSCORE, _) => (),
50 (SyntaxKind::SELF_KW, _) => return rename_to_self(&sema, position),
51 (_, Some(syntax_error)) => {
52 return Err(RenameError(format!("Invalid name `{}`: {}", new_name, syntax_error)))
53 }
54 (_, None) => {
55 return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name)))
56 }
57 },
58 None => return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name))),
34 } 59 }
35 60
36 let source_file = sema.parse(position.file_id); 61 let source_file = sema.parse(position.file_id);
@@ -103,7 +128,7 @@ fn rename_mod(
103 position: FilePosition, 128 position: FilePosition,
104 module: Module, 129 module: Module,
105 new_name: &str, 130 new_name: &str,
106) -> Option<RangeInfo<SourceChange>> { 131) -> Result<RangeInfo<SourceChange>, RenameError> {
107 let mut source_file_edits = Vec::new(); 132 let mut source_file_edits = Vec::new();
108 let mut file_system_edits = Vec::new(); 133 let mut file_system_edits = Vec::new();
109 134
@@ -125,7 +150,7 @@ fn rename_mod(
125 150
126 if let Some(src) = module.declaration_source(sema.db) { 151 if let Some(src) = module.declaration_source(sema.db) {
127 let file_id = src.file_id.original_file(sema.db); 152 let file_id = src.file_id.original_file(sema.db);
128 let name = src.value.name()?; 153 let name = src.value.name().unwrap();
129 let edit = SourceFileEdit { 154 let edit = SourceFileEdit {
130 file_id, 155 file_id,
131 edit: TextEdit::replace(name.syntax().text_range(), new_name.into()), 156 edit: TextEdit::replace(name.syntax().text_range(), new_name.into()),
@@ -133,35 +158,40 @@ fn rename_mod(
133 source_file_edits.push(edit); 158 source_file_edits.push(edit);
134 } 159 }
135 160
136 let RangeInfo { range, info: refs } = find_all_refs(sema, position, None)?; 161 let RangeInfo { range, info: refs } = find_all_refs(sema, position, None)
162 .ok_or_else(|| RenameError("No references found at position".to_string()))?;
137 let ref_edits = refs 163 let ref_edits = refs
138 .references 164 .references
139 .into_iter() 165 .into_iter()
140 .map(|reference| source_edit_from_reference(reference, new_name)); 166 .map(|reference| source_edit_from_reference(reference, new_name));
141 source_file_edits.extend(ref_edits); 167 source_file_edits.extend(ref_edits);
142 168
143 Some(RangeInfo::new(range, SourceChange::from_edits(source_file_edits, file_system_edits))) 169 Ok(RangeInfo::new(range, SourceChange::from_edits(source_file_edits, file_system_edits)))
144} 170}
145 171
146fn rename_to_self( 172fn rename_to_self(
147 sema: &Semantics<RootDatabase>, 173 sema: &Semantics<RootDatabase>,
148 position: FilePosition, 174 position: FilePosition,
149) -> Option<RangeInfo<SourceChange>> { 175) -> Result<RangeInfo<SourceChange>, RenameError> {
150 let source_file = sema.parse(position.file_id); 176 let source_file = sema.parse(position.file_id);
151 let syn = source_file.syntax(); 177 let syn = source_file.syntax();
152 178
153 let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset)?; 179 let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset)
154 let params = fn_def.param_list()?; 180 .ok_or_else(|| RenameError("No surrounding method declaration found".to_string()))?;
181 let params =
182 fn_def.param_list().ok_or_else(|| RenameError("Method has no parameters".to_string()))?;
155 if params.self_param().is_some() { 183 if params.self_param().is_some() {
156 return None; // method already has self param 184 return Err(RenameError("Method already has a self parameter".to_string()));
157 } 185 }
158 let first_param = params.params().next()?; 186 let first_param =
187 params.params().next().ok_or_else(|| RenameError("Method has no parameters".into()))?;
159 let mutable = match first_param.ty() { 188 let mutable = match first_param.ty() {
160 Some(ast::Type::RefType(rt)) => rt.mut_token().is_some(), 189 Some(ast::Type::RefType(rt)) => rt.mut_token().is_some(),
161 _ => return None, // not renaming other types 190 _ => return Err(RenameError("Not renaming other types".to_string())),
162 }; 191 };
163 192
164 let RangeInfo { range, info: refs } = find_all_refs(sema, position, None)?; 193 let RangeInfo { range, info: refs } = find_all_refs(sema, position, None)
194 .ok_or_else(|| RenameError("No reference found at position".to_string()))?;
165 195
166 let param_range = first_param.syntax().text_range(); 196 let param_range = first_param.syntax().text_range();
167 let (param_ref, usages): (Vec<Reference>, Vec<Reference>) = refs 197 let (param_ref, usages): (Vec<Reference>, Vec<Reference>) = refs
@@ -169,7 +199,7 @@ fn rename_to_self(
169 .partition(|reference| param_range.intersect(reference.file_range.range).is_some()); 199 .partition(|reference| param_range.intersect(reference.file_range.range).is_some());
170 200
171 if param_ref.is_empty() { 201 if param_ref.is_empty() {
172 return None; 202 return Err(RenameError("Parameter to rename not found".to_string()));
173 } 203 }
174 204
175 let mut edits = usages 205 let mut edits = usages
@@ -185,7 +215,7 @@ fn rename_to_self(
185 ), 215 ),
186 }); 216 });
187 217
188 Some(RangeInfo::new(range, SourceChange::from(edits))) 218 Ok(RangeInfo::new(range, SourceChange::from(edits)))
189} 219}
190 220
191fn text_edit_from_self_param( 221fn text_edit_from_self_param(
@@ -216,12 +246,13 @@ fn rename_self_to_param(
216 position: FilePosition, 246 position: FilePosition,
217 self_token: SyntaxToken, 247 self_token: SyntaxToken,
218 new_name: &str, 248 new_name: &str,
219) -> Option<RangeInfo<SourceChange>> { 249) -> Result<RangeInfo<SourceChange>, RenameError> {
220 let source_file = sema.parse(position.file_id); 250 let source_file = sema.parse(position.file_id);
221 let syn = source_file.syntax(); 251 let syn = source_file.syntax();
222 252
223 let text = sema.db.file_text(position.file_id); 253 let text = sema.db.file_text(position.file_id);
224 let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset)?; 254 let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset)
255 .ok_or_else(|| RenameError("No surrounding method declaration found".to_string()))?;
225 let search_range = fn_def.syntax().text_range(); 256 let search_range = fn_def.syntax().text_range();
226 257
227 let mut edits: Vec<SourceFileEdit> = vec![]; 258 let mut edits: Vec<SourceFileEdit> = vec![];
@@ -235,7 +266,8 @@ fn rename_self_to_param(
235 syn.token_at_offset(offset).find(|t| t.kind() == SyntaxKind::SELF_KW) 266 syn.token_at_offset(offset).find(|t| t.kind() == SyntaxKind::SELF_KW)
236 { 267 {
237 let edit = if let Some(ref self_param) = ast::SelfParam::cast(usage.parent()) { 268 let edit = if let Some(ref self_param) = ast::SelfParam::cast(usage.parent()) {
238 text_edit_from_self_param(syn, self_param, new_name)? 269 text_edit_from_self_param(syn, self_param, new_name)
270 .ok_or_else(|| RenameError("No target type found".to_string()))?
239 } else { 271 } else {
240 TextEdit::replace(usage.text_range(), String::from(new_name)) 272 TextEdit::replace(usage.text_range(), String::from(new_name))
241 }; 273 };
@@ -246,15 +278,18 @@ fn rename_self_to_param(
246 let range = ast::SelfParam::cast(self_token.parent()) 278 let range = ast::SelfParam::cast(self_token.parent())
247 .map_or(self_token.text_range(), |p| p.syntax().text_range()); 279 .map_or(self_token.text_range(), |p| p.syntax().text_range());
248 280
249 Some(RangeInfo::new(range, SourceChange::from(edits))) 281 Ok(RangeInfo::new(range, SourceChange::from(edits)))
250} 282}
251 283
252fn rename_reference( 284fn rename_reference(
253 sema: &Semantics<RootDatabase>, 285 sema: &Semantics<RootDatabase>,
254 position: FilePosition, 286 position: FilePosition,
255 new_name: &str, 287 new_name: &str,
256) -> Option<RangeInfo<SourceChange>> { 288) -> Result<RangeInfo<SourceChange>, RenameError> {
257 let RangeInfo { range, info: refs } = find_all_refs(sema, position, None)?; 289 let RangeInfo { range, info: refs } = match find_all_refs(sema, position, None) {
290 Some(range_info) => range_info,
291 None => return Err(RenameError("No references found at position".to_string())),
292 };
258 293
259 let edit = refs 294 let edit = refs
260 .into_iter() 295 .into_iter()
@@ -262,10 +297,10 @@ fn rename_reference(
262 .collect::<Vec<_>>(); 297 .collect::<Vec<_>>();
263 298
264 if edit.is_empty() { 299 if edit.is_empty() {
265 return None; 300 return Err(RenameError("No references found at position".to_string()));
266 } 301 }
267 302
268 Some(RangeInfo::new(range, SourceChange::from(edit))) 303 Ok(RangeInfo::new(range, SourceChange::from(edit)))
269} 304}
270 305
271#[cfg(test)] 306#[cfg(test)]
@@ -280,25 +315,45 @@ mod tests {
280 fn check(new_name: &str, ra_fixture_before: &str, ra_fixture_after: &str) { 315 fn check(new_name: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
281 let ra_fixture_after = &trim_indent(ra_fixture_after); 316 let ra_fixture_after = &trim_indent(ra_fixture_after);
282 let (analysis, position) = fixture::position(ra_fixture_before); 317 let (analysis, position) = fixture::position(ra_fixture_before);
283 let source_change = analysis.rename(position, new_name).unwrap(); 318 let rename_result = analysis
284 let mut text_edit_builder = TextEdit::builder(); 319 .rename(position, new_name)
285 let mut file_id: Option<FileId> = None; 320 .unwrap_or_else(|err| panic!("Rename to '{}' was cancelled: {}", new_name, err));
286 if let Some(change) = source_change { 321 match rename_result {
287 for edit in change.info.source_file_edits { 322 Ok(source_change) => {
288 file_id = Some(edit.file_id); 323 let mut text_edit_builder = TextEdit::builder();
289 for indel in edit.edit.into_iter() { 324 let mut file_id: Option<FileId> = None;
290 text_edit_builder.replace(indel.delete, indel.insert); 325 for edit in source_change.info.source_file_edits {
326 file_id = Some(edit.file_id);
327 for indel in edit.edit.into_iter() {
328 text_edit_builder.replace(indel.delete, indel.insert);
329 }
291 } 330 }
331 let mut result = analysis.file_text(file_id.unwrap()).unwrap().to_string();
332 text_edit_builder.finish().apply(&mut result);
333 assert_eq_text!(ra_fixture_after, &*result);
292 } 334 }
293 } 335 Err(err) => {
294 let mut result = analysis.file_text(file_id.unwrap()).unwrap().to_string(); 336 if ra_fixture_after.starts_with("error:") {
295 text_edit_builder.finish().apply(&mut result); 337 let error_message = ra_fixture_after
296 assert_eq_text!(ra_fixture_after, &*result); 338 .chars()
339 .into_iter()
340 .skip("error:".len())
341 .collect::<String>();
342 assert_eq!(error_message.trim(), err.to_string());
343 return;
344 } else {
345 panic!("Rename to '{}' failed unexpectedly: {}", new_name, err)
346 }
347 }
348 };
297 } 349 }
298 350
299 fn check_expect(new_name: &str, ra_fixture: &str, expect: Expect) { 351 fn check_expect(new_name: &str, ra_fixture: &str, expect: Expect) {
300 let (analysis, position) = fixture::position(ra_fixture); 352 let (analysis, position) = fixture::position(ra_fixture);
301 let source_change = analysis.rename(position, new_name).unwrap().unwrap(); 353 let source_change = analysis
354 .rename(position, new_name)
355 .unwrap()
356 .expect("Expect returned RangeInfo to be Some, but was None");
302 expect.assert_debug_eq(&source_change) 357 expect.assert_debug_eq(&source_change)
303 } 358 }
304 359
@@ -313,11 +368,30 @@ mod tests {
313 } 368 }
314 369
315 #[test] 370 #[test]
316 fn test_rename_to_invalid_identifier() { 371 fn test_rename_to_invalid_identifier1() {
317 let (analysis, position) = fixture::position(r#"fn main() { let i<|> = 1; }"#); 372 check(
318 let new_name = "invalid!"; 373 "invalid!",
319 let source_change = analysis.rename(position, new_name).unwrap(); 374 r#"fn main() { let i<|> = 1; }"#,
320 assert!(source_change.is_none()); 375 "error: Invalid name `invalid!`: not an identifier",
376 );
377 }
378
379 #[test]
380 fn test_rename_to_invalid_identifier2() {
381 check(
382 "multiple tokens",
383 r#"fn main() { let i<|> = 1; }"#,
384 "error: Invalid name `multiple tokens`: not an identifier",
385 );
386 }
387
388 #[test]
389 fn test_rename_to_invalid_identifier3() {
390 check(
391 "let",
392 r#"fn main() { let i<|> = 1; }"#,
393 "error: Invalid name `let`: not an identifier",
394 );
321 } 395 }
322 396
323 #[test] 397 #[test]
@@ -350,6 +424,15 @@ fn main() {
350 } 424 }
351 425
352 #[test] 426 #[test]
427 fn test_rename_unresolved_reference() {
428 check(
429 "new_name",
430 r#"fn main() { let _ = unresolved_ref<|>; }"#,
431 "error: No references found at position",
432 );
433 }
434
435 #[test]
353 fn test_rename_for_macro_args() { 436 fn test_rename_for_macro_args() {
354 check( 437 check(
355 "b", 438 "b",
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs
index d9fc25d88..6aafd6fd5 100644
--- a/crates/ide/src/syntax_highlighting.rs
+++ b/crates/ide/src/syntax_highlighting.rs
@@ -68,7 +68,7 @@ pub(crate) fn highlight(
68 // When we leave a node, the we use it to flatten the highlighted ranges. 68 // When we leave a node, the we use it to flatten the highlighted ranges.
69 let mut stack = HighlightedRangeStack::new(); 69 let mut stack = HighlightedRangeStack::new();
70 70
71 let mut current_macro_call: Option<ast::MacroCall> = None; 71 let mut current_macro_call: Option<(ast::MacroCall, Option<MacroMatcherParseState>)> = None;
72 let mut format_string: Option<SyntaxElement> = None; 72 let mut format_string: Option<SyntaxElement> = None;
73 73
74 // Walk all nodes, keeping track of whether we are inside a macro or not. 74 // Walk all nodes, keeping track of whether we are inside a macro or not.
@@ -92,7 +92,6 @@ pub(crate) fn highlight(
92 // Track "inside macro" state 92 // Track "inside macro" state
93 match event.clone().map(|it| it.into_node().and_then(ast::MacroCall::cast)) { 93 match event.clone().map(|it| it.into_node().and_then(ast::MacroCall::cast)) {
94 WalkEvent::Enter(Some(mc)) => { 94 WalkEvent::Enter(Some(mc)) => {
95 current_macro_call = Some(mc.clone());
96 if let Some(range) = macro_call_range(&mc) { 95 if let Some(range) = macro_call_range(&mc) {
97 stack.add(HighlightedRange { 96 stack.add(HighlightedRange {
98 range, 97 range,
@@ -100,7 +99,9 @@ pub(crate) fn highlight(
100 binding_hash: None, 99 binding_hash: None,
101 }); 100 });
102 } 101 }
102 let mut is_macro_rules = None;
103 if let Some(name) = mc.is_macro_rules() { 103 if let Some(name) = mc.is_macro_rules() {
104 is_macro_rules = Some(MacroMatcherParseState::new());
104 if let Some((highlight, binding_hash)) = highlight_element( 105 if let Some((highlight, binding_hash)) = highlight_element(
105 &sema, 106 &sema,
106 &mut bindings_shadow_count, 107 &mut bindings_shadow_count,
@@ -114,10 +115,11 @@ pub(crate) fn highlight(
114 }); 115 });
115 } 116 }
116 } 117 }
118 current_macro_call = Some((mc.clone(), is_macro_rules));
117 continue; 119 continue;
118 } 120 }
119 WalkEvent::Leave(Some(mc)) => { 121 WalkEvent::Leave(Some(mc)) => {
120 assert!(current_macro_call == Some(mc)); 122 assert!(current_macro_call.map(|it| it.0) == Some(mc));
121 current_macro_call = None; 123 current_macro_call = None;
122 format_string = None; 124 format_string = None;
123 } 125 }
@@ -146,6 +148,20 @@ pub(crate) fn highlight(
146 WalkEvent::Leave(_) => continue, 148 WalkEvent::Leave(_) => continue,
147 }; 149 };
148 150
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
149 let range = element.text_range(); 165 let range = element.text_range();
150 166
151 let element_to_highlight = if current_macro_call.is_some() && element.kind() != COMMENT { 167 let element_to_highlight = if current_macro_call.is_some() && element.kind() != COMMENT {
@@ -918,3 +934,99 @@ fn highlight_name_ref_by_syntax(name: ast::NameRef, sema: &Semantics<RootDatabas
918 _ => default.into(), 934 _ => default.into(),
919 } 935 }
920} 936}
937
938struct 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
947impl 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)]
959enum RuleState {
960 Matcher,
961 Expander,
962 Between,
963 None,
964}
965
966impl 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
977fn 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
1021fn 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/test_data/highlight_strings.html b/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html
index 1b681b2c6..43f1b32fd 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html
@@ -37,7 +37,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
37</style> 37</style>
38<pre><code><span class="macro">macro_rules!</span> <span class="macro declaration">println</span> <span class="punctuation">{</span> 38<pre><code><span class="macro">macro_rules!</span> <span class="macro declaration">println</span> <span class="punctuation">{</span>
39 <span class="punctuation">(</span><span class="punctuation">$</span><span class="punctuation">(</span><span class="punctuation">$</span>arg<span class="punctuation">:</span>tt<span class="punctuation">)</span><span class="punctuation">*</span><span class="punctuation">)</span> <span class="operator">=</span><span class="punctuation">&gt;</span> <span class="punctuation">(</span><span class="punctuation">{</span> 39 <span class="punctuation">(</span><span class="punctuation">$</span><span class="punctuation">(</span><span class="punctuation">$</span>arg<span class="punctuation">:</span>tt<span class="punctuation">)</span><span class="punctuation">*</span><span class="punctuation">)</span> <span class="operator">=</span><span class="punctuation">&gt;</span> <span class="punctuation">(</span><span class="punctuation">{</span>
40 <span class="punctuation">$</span><span class="keyword">crate</span><span class="punctuation">:</span><span class="punctuation">:</span>io<span class="punctuation">:</span><span class="punctuation">:</span>_print<span class="punctuation">(</span><span class="punctuation">$</span><span class="keyword">crate</span><span class="punctuation">:</span><span class="punctuation">:</span>format_args_nl<span class="punctuation">!</span><span class="punctuation">(</span><span class="punctuation">$</span><span class="punctuation">(</span><span class="punctuation">$</span>arg<span class="punctuation">)</span><span class="punctuation">*</span><span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">;</span> 40 <span class="punctuation">$</span>crate<span class="punctuation">:</span><span class="punctuation">:</span>io<span class="punctuation">:</span><span class="punctuation">:</span>_print<span class="punctuation">(</span><span class="punctuation">$</span>crate<span class="punctuation">:</span><span class="punctuation">:</span>format_args_nl<span class="punctuation">!</span><span class="punctuation">(</span><span class="punctuation">$</span><span class="punctuation">(</span><span class="punctuation">$</span>arg<span class="punctuation">)</span><span class="punctuation">*</span><span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">;</span>
41 <span class="punctuation">}</span><span class="punctuation">)</span> 41 <span class="punctuation">}</span><span class="punctuation">)</span>
42<span class="punctuation">}</span> 42<span class="punctuation">}</span>
43#[rustc_builtin_macro] 43#[rustc_builtin_macro]
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlighting.html b/crates/ide/src/syntax_highlighting/test_data/highlighting.html
index 1d8a3c404..0bb0928e4 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlighting.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlighting.html
@@ -62,7 +62,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
62 62
63<span class="keyword">impl</span> <span class="struct">Foo</span> <span class="punctuation">{</span> 63<span class="keyword">impl</span> <span class="struct">Foo</span> <span class="punctuation">{</span>
64 <span class="keyword">fn</span> <span class="function declaration">baz</span><span class="punctuation">(</span><span class="keyword">mut</span> <span class="self_keyword mutable">self</span><span class="punctuation">,</span> <span class="value_param declaration">f</span><span class="punctuation">:</span> <span class="struct">Foo</span><span class="punctuation">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span> <span class="punctuation">{</span> 64 <span class="keyword">fn</span> <span class="function declaration">baz</span><span class="punctuation">(</span><span class="keyword">mut</span> <span class="self_keyword mutable">self</span><span class="punctuation">,</span> <span class="value_param declaration">f</span><span class="punctuation">:</span> <span class="struct">Foo</span><span class="punctuation">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span> <span class="punctuation">{</span>
65 <span class="value_param">f</span><span class="punctuation">.</span><span class="function consuming">baz</span><span class="punctuation">(</span><span class="self_keyword consuming">self</span><span class="punctuation">)</span> 65 <span class="value_param">f</span><span class="punctuation">.</span><span class="function consuming">baz</span><span class="punctuation">(</span><span class="self_keyword mutable consuming">self</span><span class="punctuation">)</span>
66 <span class="punctuation">}</span> 66 <span class="punctuation">}</span>
67 67
68 <span class="keyword">fn</span> <span class="function declaration">qux</span><span class="punctuation">(</span><span class="operator">&</span><span class="keyword">mut</span> <span class="self_keyword mutable">self</span><span class="punctuation">)</span> <span class="punctuation">{</span> 68 <span class="keyword">fn</span> <span class="function declaration">qux</span><span class="punctuation">(</span><span class="operator">&</span><span class="keyword">mut</span> <span class="self_keyword mutable">self</span><span class="punctuation">)</span> <span class="punctuation">{</span>
@@ -115,6 +115,10 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
115 <span class="punctuation">}</span> 115 <span class="punctuation">}</span>
116<span class="punctuation">}</span> 116<span class="punctuation">}</span>
117 117
118<span class="macro">macro_rules!</span> <span class="macro declaration">keyword_frag</span> <span class="punctuation">{</span>
119 <span class="punctuation">(</span><span class="punctuation">$</span>type<span class="punctuation">:</span>ty<span class="punctuation">)</span> <span class="operator">=</span><span class="punctuation">&gt;</span> <span class="punctuation">(</span><span class="punctuation">$</span>type<span class="punctuation">)</span>
120<span class="punctuation">}</span>
121
118<span class="comment">// comment</span> 122<span class="comment">// comment</span>
119<span class="keyword">fn</span> <span class="function declaration">main</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span> 123<span class="keyword">fn</span> <span class="function declaration">main</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span>
120 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"Hello, {}!"</span><span class="punctuation">,</span> <span class="numeric_literal">92</span><span class="punctuation">)</span><span class="punctuation">;</span> 124 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"Hello, {}!"</span><span class="punctuation">,</span> <span class="numeric_literal">92</span><span class="punctuation">)</span><span class="punctuation">;</span>
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs
index 694c4b7fa..126363b8b 100644
--- a/crates/ide/src/syntax_highlighting/tests.rs
+++ b/crates/ide/src/syntax_highlighting/tests.rs
@@ -89,6 +89,10 @@ macro_rules! noop {
89 } 89 }
90} 90}
91 91
92macro_rules! keyword_frag {
93 ($type:ty) => ($type)
94}
95
92// comment 96// comment
93fn main() { 97fn main() {
94 println!("Hello, {}!", 92); 98 println!("Hello, {}!", 92);