aboutsummaryrefslogtreecommitdiff
path: root/crates/ide
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide')
-rw-r--r--crates/ide/src/completion/complete_postfix/format_like.rs2
-rw-r--r--crates/ide/src/diagnostics.rs141
-rw-r--r--crates/ide/src/diagnostics/fixes.rs24
-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.rs81
-rw-r--r--crates/ide/src/lib.rs24
-rw-r--r--crates/ide/src/prime_caches.rs43
-rw-r--r--crates/ide/src/references.rs90
-rw-r--r--crates/ide/src/references/rename.rs182
-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
14 files changed, 899 insertions, 105 deletions
diff --git a/crates/ide/src/completion/complete_postfix/format_like.rs b/crates/ide/src/completion/complete_postfix/format_like.rs
index 81c33bf3a..50d1e5c81 100644
--- a/crates/ide/src/completion/complete_postfix/format_like.rs
+++ b/crates/ide/src/completion/complete_postfix/format_like.rs
@@ -25,6 +25,7 @@ static KINDS: &[(&str, &str)] = &[
25 ("fmt", "format!"), 25 ("fmt", "format!"),
26 ("panic", "panic!"), 26 ("panic", "panic!"),
27 ("println", "println!"), 27 ("println", "println!"),
28 ("eprintln", "eprintln!"),
28 ("logd", "log::debug!"), 29 ("logd", "log::debug!"),
29 ("logt", "log::trace!"), 30 ("logt", "log::trace!"),
30 ("logi", "log::info!"), 31 ("logi", "log::info!"),
@@ -259,6 +260,7 @@ mod tests {
259 fn test_into_suggestion() { 260 fn test_into_suggestion() {
260 let test_vector = &[ 261 let test_vector = &[
261 ("println!", "{}", r#"println!("{}", $1)"#), 262 ("println!", "{}", r#"println!("{}", $1)"#),
263 ("eprintln!", "{}", r#"eprintln!("{}", $1)"#),
262 ( 264 (
263 "log::info!", 265 "log::info!",
264 "{} {expr} {} {2 + 2}", 266 "{} {expr} {} {2 + 2}",
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs
index f5d627b6e..b30cdb6ed 100644
--- a/crates/ide/src/diagnostics.rs
+++ b/crates/ide/src/diagnostics.rs
@@ -96,6 +96,9 @@ pub(crate) fn diagnostics(
96 .on::<hir::diagnostics::NoSuchField, _>(|d| { 96 .on::<hir::diagnostics::NoSuchField, _>(|d| {
97 res.borrow_mut().push(diagnostic_with_fix(d, &sema)); 97 res.borrow_mut().push(diagnostic_with_fix(d, &sema));
98 }) 98 })
99 .on::<hir::diagnostics::IncorrectCase, _>(|d| {
100 res.borrow_mut().push(warning_with_fix(d, &sema));
101 })
99 // Only collect experimental diagnostics when they're enabled. 102 // Only collect experimental diagnostics when they're enabled.
100 .filter(|diag| !(diag.is_experimental() && config.disable_experimental)) 103 .filter(|diag| !(diag.is_experimental() && config.disable_experimental))
101 .filter(|diag| !config.disabled.contains(diag.code().as_str())); 104 .filter(|diag| !config.disabled.contains(diag.code().as_str()));
@@ -130,6 +133,15 @@ fn diagnostic_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabas
130 } 133 }
131} 134}
132 135
136fn warning_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic {
137 Diagnostic {
138 range: sema.diagnostics_display_range(d).range,
139 message: d.message(),
140 severity: Severity::WeakWarning,
141 fix: d.fix(&sema),
142 }
143}
144
133fn check_unnecessary_braces_in_use_statement( 145fn check_unnecessary_braces_in_use_statement(
134 acc: &mut Vec<Diagnostic>, 146 acc: &mut Vec<Diagnostic>,
135 file_id: FileId, 147 file_id: FileId,
@@ -245,8 +257,37 @@ mod tests {
245 257
246 assert_eq_text!(&after, &actual); 258 assert_eq_text!(&after, &actual);
247 assert!( 259 assert!(
248 fix.fix_trigger_range.start() <= file_position.offset 260 fix.fix_trigger_range.contains_inclusive(file_position.offset),
249 && fix.fix_trigger_range.end() >= file_position.offset, 261 "diagnostic fix range {:?} does not touch cursor position {:?}",
262 fix.fix_trigger_range,
263 file_position.offset
264 );
265 }
266
267 /// Similar to `check_fix`, but applies all the available fixes.
268 fn check_fixes(ra_fixture_before: &str, ra_fixture_after: &str) {
269 let after = trim_indent(ra_fixture_after);
270
271 let (analysis, file_position) = fixture::position(ra_fixture_before);
272 let diagnostic = analysis
273 .diagnostics(&DiagnosticsConfig::default(), file_position.file_id)
274 .unwrap()
275 .pop()
276 .unwrap();
277 let fix = diagnostic.fix.unwrap();
278 let target_file_contents = analysis.file_text(file_position.file_id).unwrap();
279 let actual = {
280 let mut actual = target_file_contents.to_string();
281 // Go from the last one to the first one, so that ranges won't be affected by previous edits.
282 for edit in fix.source_change.source_file_edits.iter().rev() {
283 edit.edit.apply(&mut actual);
284 }
285 actual
286 };
287
288 assert_eq_text!(&after, &actual);
289 assert!(
290 fix.fix_trigger_range.contains_inclusive(file_position.offset),
250 "diagnostic fix range {:?} does not touch cursor position {:?}", 291 "diagnostic fix range {:?} does not touch cursor position {:?}",
251 fix.fix_trigger_range, 292 fix.fix_trigger_range,
252 file_position.offset 293 file_position.offset
@@ -790,4 +831,100 @@ struct Foo {
790 let diagnostics = analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap(); 831 let diagnostics = analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap();
791 assert!(!diagnostics.is_empty()); 832 assert!(!diagnostics.is_empty());
792 } 833 }
834
835 #[test]
836 fn test_rename_incorrect_case() {
837 check_fixes(
838 r#"
839pub struct test_struct<|> { one: i32 }
840
841pub fn some_fn(val: test_struct) -> test_struct {
842 test_struct { one: val.one + 1 }
843}
844"#,
845 r#"
846pub struct TestStruct { one: i32 }
847
848pub fn some_fn(val: TestStruct) -> TestStruct {
849 TestStruct { one: val.one + 1 }
850}
851"#,
852 );
853
854 check_fixes(
855 r#"
856pub fn some_fn(NonSnakeCase<|>: u8) -> u8 {
857 NonSnakeCase
858}
859"#,
860 r#"
861pub fn some_fn(non_snake_case: u8) -> u8 {
862 non_snake_case
863}
864"#,
865 );
866
867 check_fixes(
868 r#"
869pub fn SomeFn<|>(val: u8) -> u8 {
870 if val != 0 { SomeFn(val - 1) } else { val }
871}
872"#,
873 r#"
874pub fn some_fn(val: u8) -> u8 {
875 if val != 0 { some_fn(val - 1) } else { val }
876}
877"#,
878 );
879
880 check_fixes(
881 r#"
882fn some_fn() {
883 let whatAWeird_Formatting<|> = 10;
884 another_func(whatAWeird_Formatting);
885}
886"#,
887 r#"
888fn some_fn() {
889 let what_a_weird_formatting = 10;
890 another_func(what_a_weird_formatting);
891}
892"#,
893 );
894 }
895
896 #[test]
897 fn test_uppercase_const_no_diagnostics() {
898 check_no_diagnostics(
899 r#"
900fn foo() {
901 const ANOTHER_ITEM<|>: &str = "some_item";
902}
903"#,
904 );
905 }
906
907 #[test]
908 fn test_rename_incorrect_case_struct_method() {
909 check_fixes(
910 r#"
911pub struct TestStruct;
912
913impl TestStruct {
914 pub fn SomeFn<|>() -> TestStruct {
915 TestStruct
916 }
917}
918"#,
919 r#"
920pub struct TestStruct;
921
922impl TestStruct {
923 pub fn some_fn() -> TestStruct {
924 TestStruct
925 }
926}
927"#,
928 );
929 }
793} 930}
diff --git a/crates/ide/src/diagnostics/fixes.rs b/crates/ide/src/diagnostics/fixes.rs
index 68ae1c239..0c75e50b0 100644
--- a/crates/ide/src/diagnostics/fixes.rs
+++ b/crates/ide/src/diagnostics/fixes.rs
@@ -3,7 +3,10 @@
3use base_db::FileId; 3use base_db::FileId;
4use hir::{ 4use hir::{
5 db::AstDatabase, 5 db::AstDatabase,
6 diagnostics::{Diagnostic, MissingFields, MissingOkInTailExpr, NoSuchField, UnresolvedModule}, 6 diagnostics::{
7 Diagnostic, IncorrectCase, MissingFields, MissingOkInTailExpr, NoSuchField,
8 UnresolvedModule,
9 },
7 HasSource, HirDisplay, Semantics, VariantDef, 10 HasSource, HirDisplay, Semantics, VariantDef,
8}; 11};
9use ide_db::{ 12use ide_db::{
@@ -17,7 +20,7 @@ use syntax::{
17}; 20};
18use text_edit::TextEdit; 21use text_edit::TextEdit;
19 22
20use crate::diagnostics::Fix; 23use crate::{diagnostics::Fix, references::rename::rename_with_semantics, FilePosition};
21 24
22/// A [Diagnostic] that potentially has a fix available. 25/// A [Diagnostic] that potentially has a fix available.
23/// 26///
@@ -99,6 +102,23 @@ impl DiagnosticWithFix for MissingOkInTailExpr {
99 } 102 }
100} 103}
101 104
105impl DiagnosticWithFix for IncorrectCase {
106 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> {
107 let root = sema.db.parse_or_expand(self.file)?;
108 let name_node = self.ident.to_node(&root);
109
110 let file_id = self.file.original_file(sema.db);
111 let offset = name_node.syntax().text_range().start();
112 let file_position = FilePosition { file_id, offset };
113
114 let rename_changes =
115 rename_with_semantics(sema, file_position, &self.suggested_text).ok()?;
116
117 let label = format!("Rename to {}", self.suggested_text);
118 Some(Fix::new(&label, rename_changes.info, rename_changes.range))
119 }
120}
121
102fn missing_record_expr_field_fix( 122fn missing_record_expr_field_fix(
103 sema: &Semantics<RootDatabase>, 123 sema: &Semantics<RootDatabase>,
104 usage_file_id: FileId, 124 usage_file_id: FileId,
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..e2079bbcf 100644
--- a/crates/ide/src/inlay_hints.rs
+++ b/crates/ide/src/inlay_hints.rs
@@ -220,8 +220,8 @@ fn hint_iterator(
220 } 220 }
221 let iter_trait = FamousDefs(sema, krate).core_iter_Iterator()?; 221 let iter_trait = FamousDefs(sema, krate).core_iter_Iterator()?;
222 let iter_mod = FamousDefs(sema, krate).core_iter()?; 222 let iter_mod = FamousDefs(sema, krate).core_iter()?;
223 // assert this type comes from `core::iter` 223 // assert this struct comes from `core::iter`
224 iter_mod.visibility_of(db, &iter_trait.into()).filter(|&vis| vis == hir::Visibility::Public)?; 224 iter_mod.visibility_of(db, &strukt.into()).filter(|&vis| vis == hir::Visibility::Public)?;
225 if ty.impls_trait(db, iter_trait, &[]) { 225 if ty.impls_trait(db, iter_trait, &[]) {
226 let assoc_type_item = iter_trait.items(db).into_iter().find_map(|item| match item { 226 let assoc_type_item = iter_trait.items(db).into_iter().find_map(|item| match item {
227 hir::AssocItem::TypeAlias(alias) if alias.name(db) == known::Item => Some(alias), 227 hir::AssocItem::TypeAlias(alias) if alias.name(db) == known::Item => Some(alias),
@@ -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 e0830eb4f..88e2f2db3 100644
--- a/crates/ide/src/references.rs
+++ b/crates/ide/src/references.rs
@@ -9,7 +9,7 @@
9//! at the index that the match starts at and its tree parent is 9//! at the index that the match starts at and its tree parent is
10//! resolved to the search element definition, we get a reference. 10//! resolved to the search element definition, we get a reference.
11 11
12mod rename; 12pub(crate) mod rename;
13 13
14use hir::Semantics; 14use hir::Semantics;
15use ide_db::{ 15use ide_db::{
@@ -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
@@ -406,6 +407,23 @@ enum Foo {
406 } 407 }
407 408
408 #[test] 409 #[test]
410 fn test_find_all_refs_enum_var_field() {
411 check(
412 r#"
413enum Foo {
414 A,
415 B { field<|>: u8 },
416 C,
417}
418"#,
419 expect![[r#"
420 field RECORD_FIELD FileId(0) 26..35 26..31 Other
421
422 "#]],
423 );
424 }
425
426 #[test]
409 fn test_find_all_refs_two_modules() { 427 fn test_find_all_refs_two_modules() {
410 check( 428 check(
411 r#" 429 r#"
@@ -669,6 +687,76 @@ fn g() { f(); }
669 ); 687 );
670 } 688 }
671 689
690 #[test]
691 fn test_find_all_refs_struct_pat() {
692 check(
693 r#"
694struct S {
695 field<|>: u8,
696}
697
698fn f(s: S) {
699 match s {
700 S { field } => {}
701 }
702}
703"#,
704 expect![[r#"
705 field RECORD_FIELD FileId(0) 15..24 15..20 Other
706
707 FileId(0) 68..73 FieldShorthandForField Read
708 "#]],
709 );
710 }
711
712 #[test]
713 fn test_find_all_refs_enum_var_pat() {
714 check(
715 r#"
716enum En {
717 Variant {
718 field<|>: u8,
719 }
720}
721
722fn f(e: En) {
723 match e {
724 En::Variant { field } => {}
725 }
726}
727"#,
728 expect![[r#"
729 field RECORD_FIELD FileId(0) 32..41 32..37 Other
730
731 FileId(0) 102..107 FieldShorthandForField Read
732 "#]],
733 );
734 }
735
736 #[test]
737 fn test_find_all_refs_enum_var_privacy() {
738 check(
739 r#"
740mod m {
741 pub enum En {
742 Variant {
743 field<|>: u8,
744 }
745 }
746}
747
748fn f() -> m::En {
749 m::En::Variant { field: 0 }
750}
751"#,
752 expect![[r#"
753 field RECORD_FIELD FileId(0) 56..65 56..61 Other
754
755 FileId(0) 125..130 Other Read
756 "#]],
757 );
758 }
759
672 fn check(ra_fixture: &str, expect: Expect) { 760 fn check(ra_fixture: &str, expect: Expect) {
673 check_with_scope(ra_fixture, None, expect) 761 check_with_scope(ra_fixture, None, expect)
674 } 762 }
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs
index 8cbe1ae5a..f9a11e43d 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,44 @@ 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);
45 rename_with_semantics(&sema, position, new_name)
46}
29 47
30 match lex_single_valid_syntax_kind(new_name)? { 48pub(crate) fn rename_with_semantics(
31 SyntaxKind::IDENT | SyntaxKind::UNDERSCORE => (), 49 sema: &Semantics<RootDatabase>,
32 SyntaxKind::SELF_KW => return rename_to_self(&sema, position), 50 position: FilePosition,
33 _ => return None, 51 new_name: &str,
52) -> Result<RangeInfo<SourceChange>, RenameError> {
53 match lex_single_syntax_kind(new_name) {
54 Some(res) => match res {
55 (SyntaxKind::IDENT, _) => (),
56 (SyntaxKind::UNDERSCORE, _) => (),
57 (SyntaxKind::SELF_KW, _) => return rename_to_self(&sema, position),
58 (_, Some(syntax_error)) => {
59 return Err(RenameError(format!("Invalid name `{}`: {}", new_name, syntax_error)))
60 }
61 (_, None) => {
62 return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name)))
63 }
64 },
65 None => return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name))),
34 } 66 }
35 67
36 let source_file = sema.parse(position.file_id); 68 let source_file = sema.parse(position.file_id);
@@ -103,7 +135,7 @@ fn rename_mod(
103 position: FilePosition, 135 position: FilePosition,
104 module: Module, 136 module: Module,
105 new_name: &str, 137 new_name: &str,
106) -> Option<RangeInfo<SourceChange>> { 138) -> Result<RangeInfo<SourceChange>, RenameError> {
107 let mut source_file_edits = Vec::new(); 139 let mut source_file_edits = Vec::new();
108 let mut file_system_edits = Vec::new(); 140 let mut file_system_edits = Vec::new();
109 141
@@ -125,7 +157,7 @@ fn rename_mod(
125 157
126 if let Some(src) = module.declaration_source(sema.db) { 158 if let Some(src) = module.declaration_source(sema.db) {
127 let file_id = src.file_id.original_file(sema.db); 159 let file_id = src.file_id.original_file(sema.db);
128 let name = src.value.name()?; 160 let name = src.value.name().unwrap();
129 let edit = SourceFileEdit { 161 let edit = SourceFileEdit {
130 file_id, 162 file_id,
131 edit: TextEdit::replace(name.syntax().text_range(), new_name.into()), 163 edit: TextEdit::replace(name.syntax().text_range(), new_name.into()),
@@ -133,35 +165,40 @@ fn rename_mod(
133 source_file_edits.push(edit); 165 source_file_edits.push(edit);
134 } 166 }
135 167
136 let RangeInfo { range, info: refs } = find_all_refs(sema, position, None)?; 168 let RangeInfo { range, info: refs } = find_all_refs(sema, position, None)
169 .ok_or_else(|| RenameError("No references found at position".to_string()))?;
137 let ref_edits = refs 170 let ref_edits = refs
138 .references 171 .references
139 .into_iter() 172 .into_iter()
140 .map(|reference| source_edit_from_reference(reference, new_name)); 173 .map(|reference| source_edit_from_reference(reference, new_name));
141 source_file_edits.extend(ref_edits); 174 source_file_edits.extend(ref_edits);
142 175
143 Some(RangeInfo::new(range, SourceChange::from_edits(source_file_edits, file_system_edits))) 176 Ok(RangeInfo::new(range, SourceChange::from_edits(source_file_edits, file_system_edits)))
144} 177}
145 178
146fn rename_to_self( 179fn rename_to_self(
147 sema: &Semantics<RootDatabase>, 180 sema: &Semantics<RootDatabase>,
148 position: FilePosition, 181 position: FilePosition,
149) -> Option<RangeInfo<SourceChange>> { 182) -> Result<RangeInfo<SourceChange>, RenameError> {
150 let source_file = sema.parse(position.file_id); 183 let source_file = sema.parse(position.file_id);
151 let syn = source_file.syntax(); 184 let syn = source_file.syntax();
152 185
153 let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset)?; 186 let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset)
154 let params = fn_def.param_list()?; 187 .ok_or_else(|| RenameError("No surrounding method declaration found".to_string()))?;
188 let params =
189 fn_def.param_list().ok_or_else(|| RenameError("Method has no parameters".to_string()))?;
155 if params.self_param().is_some() { 190 if params.self_param().is_some() {
156 return None; // method already has self param 191 return Err(RenameError("Method already has a self parameter".to_string()));
157 } 192 }
158 let first_param = params.params().next()?; 193 let first_param =
194 params.params().next().ok_or_else(|| RenameError("Method has no parameters".into()))?;
159 let mutable = match first_param.ty() { 195 let mutable = match first_param.ty() {
160 Some(ast::Type::RefType(rt)) => rt.mut_token().is_some(), 196 Some(ast::Type::RefType(rt)) => rt.mut_token().is_some(),
161 _ => return None, // not renaming other types 197 _ => return Err(RenameError("Not renaming other types".to_string())),
162 }; 198 };
163 199
164 let RangeInfo { range, info: refs } = find_all_refs(sema, position, None)?; 200 let RangeInfo { range, info: refs } = find_all_refs(sema, position, None)
201 .ok_or_else(|| RenameError("No reference found at position".to_string()))?;
165 202
166 let param_range = first_param.syntax().text_range(); 203 let param_range = first_param.syntax().text_range();
167 let (param_ref, usages): (Vec<Reference>, Vec<Reference>) = refs 204 let (param_ref, usages): (Vec<Reference>, Vec<Reference>) = refs
@@ -169,7 +206,7 @@ fn rename_to_self(
169 .partition(|reference| param_range.intersect(reference.file_range.range).is_some()); 206 .partition(|reference| param_range.intersect(reference.file_range.range).is_some());
170 207
171 if param_ref.is_empty() { 208 if param_ref.is_empty() {
172 return None; 209 return Err(RenameError("Parameter to rename not found".to_string()));
173 } 210 }
174 211
175 let mut edits = usages 212 let mut edits = usages
@@ -185,7 +222,7 @@ fn rename_to_self(
185 ), 222 ),
186 }); 223 });
187 224
188 Some(RangeInfo::new(range, SourceChange::from(edits))) 225 Ok(RangeInfo::new(range, SourceChange::from(edits)))
189} 226}
190 227
191fn text_edit_from_self_param( 228fn text_edit_from_self_param(
@@ -216,12 +253,13 @@ fn rename_self_to_param(
216 position: FilePosition, 253 position: FilePosition,
217 self_token: SyntaxToken, 254 self_token: SyntaxToken,
218 new_name: &str, 255 new_name: &str,
219) -> Option<RangeInfo<SourceChange>> { 256) -> Result<RangeInfo<SourceChange>, RenameError> {
220 let source_file = sema.parse(position.file_id); 257 let source_file = sema.parse(position.file_id);
221 let syn = source_file.syntax(); 258 let syn = source_file.syntax();
222 259
223 let text = sema.db.file_text(position.file_id); 260 let text = sema.db.file_text(position.file_id);
224 let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset)?; 261 let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset)
262 .ok_or_else(|| RenameError("No surrounding method declaration found".to_string()))?;
225 let search_range = fn_def.syntax().text_range(); 263 let search_range = fn_def.syntax().text_range();
226 264
227 let mut edits: Vec<SourceFileEdit> = vec![]; 265 let mut edits: Vec<SourceFileEdit> = vec![];
@@ -235,7 +273,8 @@ fn rename_self_to_param(
235 syn.token_at_offset(offset).find(|t| t.kind() == SyntaxKind::SELF_KW) 273 syn.token_at_offset(offset).find(|t| t.kind() == SyntaxKind::SELF_KW)
236 { 274 {
237 let edit = if let Some(ref self_param) = ast::SelfParam::cast(usage.parent()) { 275 let edit = if let Some(ref self_param) = ast::SelfParam::cast(usage.parent()) {
238 text_edit_from_self_param(syn, self_param, new_name)? 276 text_edit_from_self_param(syn, self_param, new_name)
277 .ok_or_else(|| RenameError("No target type found".to_string()))?
239 } else { 278 } else {
240 TextEdit::replace(usage.text_range(), String::from(new_name)) 279 TextEdit::replace(usage.text_range(), String::from(new_name))
241 }; 280 };
@@ -246,15 +285,18 @@ fn rename_self_to_param(
246 let range = ast::SelfParam::cast(self_token.parent()) 285 let range = ast::SelfParam::cast(self_token.parent())
247 .map_or(self_token.text_range(), |p| p.syntax().text_range()); 286 .map_or(self_token.text_range(), |p| p.syntax().text_range());
248 287
249 Some(RangeInfo::new(range, SourceChange::from(edits))) 288 Ok(RangeInfo::new(range, SourceChange::from(edits)))
250} 289}
251 290
252fn rename_reference( 291fn rename_reference(
253 sema: &Semantics<RootDatabase>, 292 sema: &Semantics<RootDatabase>,
254 position: FilePosition, 293 position: FilePosition,
255 new_name: &str, 294 new_name: &str,
256) -> Option<RangeInfo<SourceChange>> { 295) -> Result<RangeInfo<SourceChange>, RenameError> {
257 let RangeInfo { range, info: refs } = find_all_refs(sema, position, None)?; 296 let RangeInfo { range, info: refs } = match find_all_refs(sema, position, None) {
297 Some(range_info) => range_info,
298 None => return Err(RenameError("No references found at position".to_string())),
299 };
258 300
259 let edit = refs 301 let edit = refs
260 .into_iter() 302 .into_iter()
@@ -262,10 +304,10 @@ fn rename_reference(
262 .collect::<Vec<_>>(); 304 .collect::<Vec<_>>();
263 305
264 if edit.is_empty() { 306 if edit.is_empty() {
265 return None; 307 return Err(RenameError("No references found at position".to_string()));
266 } 308 }
267 309
268 Some(RangeInfo::new(range, SourceChange::from(edit))) 310 Ok(RangeInfo::new(range, SourceChange::from(edit)))
269} 311}
270 312
271#[cfg(test)] 313#[cfg(test)]
@@ -280,25 +322,45 @@ mod tests {
280 fn check(new_name: &str, ra_fixture_before: &str, ra_fixture_after: &str) { 322 fn check(new_name: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
281 let ra_fixture_after = &trim_indent(ra_fixture_after); 323 let ra_fixture_after = &trim_indent(ra_fixture_after);
282 let (analysis, position) = fixture::position(ra_fixture_before); 324 let (analysis, position) = fixture::position(ra_fixture_before);
283 let source_change = analysis.rename(position, new_name).unwrap(); 325 let rename_result = analysis
284 let mut text_edit_builder = TextEdit::builder(); 326 .rename(position, new_name)
285 let mut file_id: Option<FileId> = None; 327 .unwrap_or_else(|err| panic!("Rename to '{}' was cancelled: {}", new_name, err));
286 if let Some(change) = source_change { 328 match rename_result {
287 for edit in change.info.source_file_edits { 329 Ok(source_change) => {
288 file_id = Some(edit.file_id); 330 let mut text_edit_builder = TextEdit::builder();
289 for indel in edit.edit.into_iter() { 331 let mut file_id: Option<FileId> = None;
290 text_edit_builder.replace(indel.delete, indel.insert); 332 for edit in source_change.info.source_file_edits {
333 file_id = Some(edit.file_id);
334 for indel in edit.edit.into_iter() {
335 text_edit_builder.replace(indel.delete, indel.insert);
336 }
291 } 337 }
338 let mut result = analysis.file_text(file_id.unwrap()).unwrap().to_string();
339 text_edit_builder.finish().apply(&mut result);
340 assert_eq_text!(ra_fixture_after, &*result);
292 } 341 }
293 } 342 Err(err) => {
294 let mut result = analysis.file_text(file_id.unwrap()).unwrap().to_string(); 343 if ra_fixture_after.starts_with("error:") {
295 text_edit_builder.finish().apply(&mut result); 344 let error_message = ra_fixture_after
296 assert_eq_text!(ra_fixture_after, &*result); 345 .chars()
346 .into_iter()
347 .skip("error:".len())
348 .collect::<String>();
349 assert_eq!(error_message.trim(), err.to_string());
350 return;
351 } else {
352 panic!("Rename to '{}' failed unexpectedly: {}", new_name, err)
353 }
354 }
355 };
297 } 356 }
298 357
299 fn check_expect(new_name: &str, ra_fixture: &str, expect: Expect) { 358 fn check_expect(new_name: &str, ra_fixture: &str, expect: Expect) {
300 let (analysis, position) = fixture::position(ra_fixture); 359 let (analysis, position) = fixture::position(ra_fixture);
301 let source_change = analysis.rename(position, new_name).unwrap().unwrap(); 360 let source_change = analysis
361 .rename(position, new_name)
362 .unwrap()
363 .expect("Expect returned RangeInfo to be Some, but was None");
302 expect.assert_debug_eq(&source_change) 364 expect.assert_debug_eq(&source_change)
303 } 365 }
304 366
@@ -313,11 +375,30 @@ mod tests {
313 } 375 }
314 376
315 #[test] 377 #[test]
316 fn test_rename_to_invalid_identifier() { 378 fn test_rename_to_invalid_identifier1() {
317 let (analysis, position) = fixture::position(r#"fn main() { let i<|> = 1; }"#); 379 check(
318 let new_name = "invalid!"; 380 "invalid!",
319 let source_change = analysis.rename(position, new_name).unwrap(); 381 r#"fn main() { let i<|> = 1; }"#,
320 assert!(source_change.is_none()); 382 "error: Invalid name `invalid!`: not an identifier",
383 );
384 }
385
386 #[test]
387 fn test_rename_to_invalid_identifier2() {
388 check(
389 "multiple tokens",
390 r#"fn main() { let i<|> = 1; }"#,
391 "error: Invalid name `multiple tokens`: not an identifier",
392 );
393 }
394
395 #[test]
396 fn test_rename_to_invalid_identifier3() {
397 check(
398 "let",
399 r#"fn main() { let i<|> = 1; }"#,
400 "error: Invalid name `let`: not an identifier",
401 );
321 } 402 }
322 403
323 #[test] 404 #[test]
@@ -350,6 +431,15 @@ fn main() {
350 } 431 }
351 432
352 #[test] 433 #[test]
434 fn test_rename_unresolved_reference() {
435 check(
436 "new_name",
437 r#"fn main() { let _ = unresolved_ref<|>; }"#,
438 "error: No references found at position",
439 );
440 }
441
442 #[test]
353 fn test_rename_for_macro_args() { 443 fn test_rename_for_macro_args() {
354 check( 444 check(
355 "b", 445 "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);