aboutsummaryrefslogtreecommitdiff
path: root/crates/ide
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide')
-rw-r--r--crates/ide/src/folding_ranges.rs55
-rw-r--r--crates/ide/src/hover.rs164
-rw-r--r--crates/ide/src/join_lines.rs74
-rw-r--r--crates/ide/src/references/rename.rs378
-rw-r--r--crates/ide/src/runnables.rs2
5 files changed, 450 insertions, 223 deletions
diff --git a/crates/ide/src/folding_ranges.rs b/crates/ide/src/folding_ranges.rs
index 45170dd29..4b1b24562 100644
--- a/crates/ide/src/folding_ranges.rs
+++ b/crates/ide/src/folding_ranges.rs
@@ -6,7 +6,7 @@ use syntax::{
6 ast::{self, AstNode, AstToken, VisibilityOwner}, 6 ast::{self, AstNode, AstToken, VisibilityOwner},
7 Direction, NodeOrToken, SourceFile, 7 Direction, NodeOrToken, SourceFile,
8 SyntaxKind::{self, *}, 8 SyntaxKind::{self, *},
9 SyntaxNode, TextRange, 9 SyntaxNode, TextRange, TextSize,
10}; 10};
11 11
12#[derive(Debug, PartialEq, Eq)] 12#[derive(Debug, PartialEq, Eq)]
@@ -16,6 +16,7 @@ pub enum FoldKind {
16 Mods, 16 Mods,
17 Block, 17 Block,
18 ArgList, 18 ArgList,
19 Region,
19} 20}
20 21
21#[derive(Debug)] 22#[derive(Debug)]
@@ -29,6 +30,8 @@ pub(crate) fn folding_ranges(file: &SourceFile) -> Vec<Fold> {
29 let mut visited_comments = FxHashSet::default(); 30 let mut visited_comments = FxHashSet::default();
30 let mut visited_imports = FxHashSet::default(); 31 let mut visited_imports = FxHashSet::default();
31 let mut visited_mods = FxHashSet::default(); 32 let mut visited_mods = FxHashSet::default();
33 // regions can be nested, here is a LIFO buffer
34 let mut regions_starts: Vec<TextSize> = vec![];
32 35
33 for element in file.syntax().descendants_with_tokens() { 36 for element in file.syntax().descendants_with_tokens() {
34 // Fold items that span multiple lines 37 // Fold items that span multiple lines
@@ -48,10 +51,25 @@ pub(crate) fn folding_ranges(file: &SourceFile) -> Vec<Fold> {
48 // Fold groups of comments 51 // Fold groups of comments
49 if let Some(comment) = ast::Comment::cast(token) { 52 if let Some(comment) = ast::Comment::cast(token) {
50 if !visited_comments.contains(&comment) { 53 if !visited_comments.contains(&comment) {
51 if let Some(range) = 54 // regions are not real comments
52 contiguous_range_for_comment(comment, &mut visited_comments) 55 if comment.text().trim().starts_with("// region:") {
53 { 56 regions_starts.push(comment.syntax().text_range().start());
54 res.push(Fold { range, kind: FoldKind::Comment }) 57 } else if comment.text().trim().starts_with("// endregion") {
58 if let Some(region) = regions_starts.pop() {
59 res.push(Fold {
60 range: TextRange::new(
61 region,
62 comment.syntax().text_range().end(),
63 ),
64 kind: FoldKind::Region,
65 })
66 }
67 } else {
68 if let Some(range) =
69 contiguous_range_for_comment(comment, &mut visited_comments)
70 {
71 res.push(Fold { range, kind: FoldKind::Comment })
72 }
55 } 73 }
56 } 74 }
57 } 75 }
@@ -175,9 +193,16 @@ fn contiguous_range_for_comment(
175 } 193 }
176 if let Some(c) = ast::Comment::cast(token) { 194 if let Some(c) = ast::Comment::cast(token) {
177 if c.kind() == group_kind { 195 if c.kind() == group_kind {
178 visited.insert(c.clone()); 196 // regions are not real comments
179 last = c; 197 if c.text().trim().starts_with("// region:")
180 continue; 198 || c.text().trim().starts_with("// endregion")
199 {
200 break;
201 } else {
202 visited.insert(c.clone());
203 last = c;
204 continue;
205 }
181 } 206 }
182 } 207 }
183 // The comment group ends because either: 208 // The comment group ends because either:
@@ -224,6 +249,7 @@ mod tests {
224 FoldKind::Mods => "mods", 249 FoldKind::Mods => "mods",
225 FoldKind::Block => "block", 250 FoldKind::Block => "block",
226 FoldKind::ArgList => "arglist", 251 FoldKind::ArgList => "arglist",
252 FoldKind::Region => "region",
227 }; 253 };
228 assert_eq!(kind, &attr.unwrap()); 254 assert_eq!(kind, &attr.unwrap());
229 } 255 }
@@ -418,4 +444,17 @@ fn foo<fold arglist>(
418"#, 444"#,
419 ) 445 )
420 } 446 }
447
448 #[test]
449 fn fold_region() {
450 check(
451 r#"
452// 1. some normal comment
453<fold region>// region: test
454// 2. some normal comment
455calling_function(x,y);
456// endregion: test</fold>
457"#,
458 )
459 }
421} 460}
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index 9a605b09d..a9454cfa3 100644
--- a/crates/ide/src/hover.rs
+++ b/crates/ide/src/hover.rs
@@ -5,6 +5,7 @@ use hir::{
5use ide_db::{ 5use ide_db::{
6 base_db::SourceDatabase, 6 base_db::SourceDatabase,
7 defs::{Definition, NameClass, NameRefClass}, 7 defs::{Definition, NameClass, NameRefClass},
8 helpers::FamousDefs,
8 RootDatabase, 9 RootDatabase,
9}; 10};
10use itertools::Itertools; 11use itertools::Itertools;
@@ -107,16 +108,14 @@ pub(crate) fn hover(
107 } 108 }
108 }; 109 };
109 if let Some(definition) = definition { 110 if let Some(definition) = definition {
110 if let Some(markup) = hover_for_definition(db, definition) { 111 let famous_defs = match &definition {
111 let markup = markup.as_str(); 112 Definition::ModuleDef(ModuleDef::BuiltinType(_)) => {
112 let markup = if !markdown { 113 Some(FamousDefs(&sema, sema.scope(&node).krate()))
113 remove_markdown(markup) 114 }
114 } else if links_in_hover { 115 _ => None,
115 rewrite_links(db, markup, &definition) 116 };
116 } else { 117 if let Some(markup) = hover_for_definition(db, definition, famous_defs.as_ref()) {
117 remove_links(markup) 118 res.markup = process_markup(sema.db, definition, &markup, links_in_hover, markdown);
118 };
119 res.markup = Markup::from(markup);
120 if let Some(action) = show_implementations_action(db, definition) { 119 if let Some(action) = show_implementations_action(db, definition) {
121 res.actions.push(action); 120 res.actions.push(action);
122 } 121 }
@@ -138,6 +137,9 @@ pub(crate) fn hover(
138 // don't highlight the entire parent node on comment hover 137 // don't highlight the entire parent node on comment hover
139 return None; 138 return None;
140 } 139 }
140 if let res @ Some(_) = hover_for_keyword(&sema, links_in_hover, markdown, &token) {
141 return res;
142 }
141 143
142 let node = token 144 let node = token
143 .ancestors() 145 .ancestors()
@@ -272,6 +274,24 @@ fn hover_markup(
272 } 274 }
273} 275}
274 276
277fn process_markup(
278 db: &RootDatabase,
279 def: Definition,
280 markup: &Markup,
281 links_in_hover: bool,
282 markdown: bool,
283) -> Markup {
284 let markup = markup.as_str();
285 let markup = if !markdown {
286 remove_markdown(markup)
287 } else if links_in_hover {
288 rewrite_links(db, markup, &def)
289 } else {
290 remove_links(markup)
291 };
292 Markup::from(markup)
293}
294
275fn definition_owner_name(db: &RootDatabase, def: &Definition) -> Option<String> { 295fn definition_owner_name(db: &RootDatabase, def: &Definition) -> Option<String> {
276 match def { 296 match def {
277 Definition::Field(f) => Some(f.parent_def(db).name(db)), 297 Definition::Field(f) => Some(f.parent_def(db).name(db)),
@@ -304,7 +324,11 @@ fn definition_mod_path(db: &RootDatabase, def: &Definition) -> Option<String> {
304 def.module(db).map(|module| render_path(db, module, definition_owner_name(db, def))) 324 def.module(db).map(|module| render_path(db, module, definition_owner_name(db, def)))
305} 325}
306 326
307fn hover_for_definition(db: &RootDatabase, def: Definition) -> Option<Markup> { 327fn hover_for_definition(
328 db: &RootDatabase,
329 def: Definition,
330 famous_defs: Option<&FamousDefs>,
331) -> Option<Markup> {
308 let mod_path = definition_mod_path(db, &def); 332 let mod_path = definition_mod_path(db, &def);
309 return match def { 333 return match def {
310 Definition::Macro(it) => { 334 Definition::Macro(it) => {
@@ -339,7 +363,9 @@ fn hover_for_definition(db: &RootDatabase, def: Definition) -> Option<Markup> {
339 ModuleDef::Static(it) => from_def_source(db, it, mod_path), 363 ModuleDef::Static(it) => from_def_source(db, it, mod_path),
340 ModuleDef::Trait(it) => from_def_source(db, it, mod_path), 364 ModuleDef::Trait(it) => from_def_source(db, it, mod_path),
341 ModuleDef::TypeAlias(it) => from_def_source(db, it, mod_path), 365 ModuleDef::TypeAlias(it) => from_def_source(db, it, mod_path),
342 ModuleDef::BuiltinType(it) => Some(Markup::fenced_block(&it.name())), 366 ModuleDef::BuiltinType(it) => famous_defs
367 .and_then(|fd| hover_for_builtin(fd, it))
368 .or_else(|| Some(Markup::fenced_block(&it.name()))),
343 }, 369 },
344 Definition::Local(it) => Some(Markup::fenced_block(&it.ty(db).display(db))), 370 Definition::Local(it) => Some(Markup::fenced_block(&it.ty(db).display(db))),
345 Definition::SelfType(impl_def) => { 371 Definition::SelfType(impl_def) => {
@@ -380,11 +406,52 @@ fn hover_for_definition(db: &RootDatabase, def: Definition) -> Option<Markup> {
380 } 406 }
381} 407}
382 408
409fn hover_for_keyword(
410 sema: &Semantics<RootDatabase>,
411 links_in_hover: bool,
412 markdown: bool,
413 token: &SyntaxToken,
414) -> Option<RangeInfo<HoverResult>> {
415 if !token.kind().is_keyword() {
416 return None;
417 }
418 let famous_defs = FamousDefs(&sema, sema.scope(&token.parent()).krate());
419 // std exposes {}_keyword modules with docstrings on the root to document keywords
420 let keyword_mod = format!("{}_keyword", token.text());
421 let doc_owner = find_std_module(&famous_defs, &keyword_mod)?;
422 let docs = doc_owner.attrs(sema.db).docs()?;
423 let markup = process_markup(
424 sema.db,
425 Definition::ModuleDef(doc_owner.into()),
426 &hover_markup(Some(docs.into()), Some(token.text().into()), None)?,
427 links_in_hover,
428 markdown,
429 );
430 Some(RangeInfo::new(token.text_range(), HoverResult { markup, actions: Default::default() }))
431}
432
433fn hover_for_builtin(famous_defs: &FamousDefs, builtin: hir::BuiltinType) -> Option<Markup> {
434 // std exposes prim_{} modules with docstrings on the root to document the builtins
435 let primitive_mod = format!("prim_{}", builtin.name());
436 let doc_owner = find_std_module(famous_defs, &primitive_mod)?;
437 let docs = doc_owner.attrs(famous_defs.0.db).docs()?;
438 hover_markup(Some(docs.into()), Some(builtin.name().to_string()), None)
439}
440
441fn find_std_module(famous_defs: &FamousDefs, name: &str) -> Option<hir::Module> {
442 let db = famous_defs.0.db;
443 let std_crate = famous_defs.std()?;
444 let std_root_module = std_crate.root_module(db);
445 std_root_module
446 .children(db)
447 .find(|module| module.name(db).map_or(false, |module| module.to_string() == name))
448}
449
383fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { 450fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
384 return tokens.max_by_key(priority); 451 return tokens.max_by_key(priority);
385 fn priority(n: &SyntaxToken) -> usize { 452 fn priority(n: &SyntaxToken) -> usize {
386 match n.kind() { 453 match n.kind() {
387 IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] => 3, 454 IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | T![super] | T![crate] => 3,
388 T!['('] | T![')'] => 2, 455 T!['('] | T![')'] => 2,
389 kind if kind.is_trivia() => 0, 456 kind if kind.is_trivia() => 0,
390 _ => 1, 457 _ => 1,
@@ -3496,4 +3563,75 @@ mod foo$0;
3496 "#]], 3563 "#]],
3497 ); 3564 );
3498 } 3565 }
3566
3567 #[test]
3568 fn hover_self_in_use() {
3569 check(
3570 r#"
3571//! This should not appear
3572mod foo {
3573 /// But this should appear
3574 pub mod bar {}
3575}
3576use foo::bar::{self$0};
3577"#,
3578 expect![[r#"
3579 *self*
3580
3581 ```rust
3582 test::foo
3583 ```
3584
3585 ```rust
3586 pub mod bar
3587 ```
3588
3589 ---
3590
3591 But this should appear
3592 "#]],
3593 )
3594 }
3595
3596 #[test]
3597 fn hover_keyword() {
3598 let ra_fixture = r#"//- /main.rs crate:main deps:std
3599fn f() { retur$0n; }"#;
3600 let fixture = format!("{}\n{}", ra_fixture, FamousDefs::FIXTURE);
3601 check(
3602 &fixture,
3603 expect![[r#"
3604 *return*
3605
3606 ```rust
3607 return
3608 ```
3609
3610 ---
3611
3612 Docs for return_keyword
3613 "#]],
3614 );
3615 }
3616
3617 #[test]
3618 fn hover_builtin() {
3619 let ra_fixture = r#"//- /main.rs crate:main deps:std
3620cosnt _: &str$0 = ""; }"#;
3621 let fixture = format!("{}\n{}", ra_fixture, FamousDefs::FIXTURE);
3622 check(
3623 &fixture,
3624 expect![[r#"
3625 *str*
3626
3627 ```rust
3628 str
3629 ```
3630
3631 ---
3632
3633 Docs for prim_str
3634 "#]],
3635 );
3636 }
3499} 3637}
diff --git a/crates/ide/src/join_lines.rs b/crates/ide/src/join_lines.rs
index 2c077ed1f..7fcae13e0 100644
--- a/crates/ide/src/join_lines.rs
+++ b/crates/ide/src/join_lines.rs
@@ -7,6 +7,7 @@ use syntax::{
7 SyntaxKind::{self, USE_TREE, WHITESPACE}, 7 SyntaxKind::{self, USE_TREE, WHITESPACE},
8 SyntaxNode, SyntaxToken, TextRange, TextSize, T, 8 SyntaxNode, SyntaxToken, TextRange, TextSize, T,
9}; 9};
10use test_utils::mark;
10use text_edit::{TextEdit, TextEditBuilder}; 11use text_edit::{TextEdit, TextEditBuilder};
11 12
12// Feature: Join Lines 13// Feature: Join Lines
@@ -44,9 +45,9 @@ pub(crate) fn join_lines(file: &SourceFile, range: TextRange) -> TextEdit {
44 let text = token.text(); 45 let text = token.text();
45 for (pos, _) in text[range].bytes().enumerate().filter(|&(_, b)| b == b'\n') { 46 for (pos, _) in text[range].bytes().enumerate().filter(|&(_, b)| b == b'\n') {
46 let pos: TextSize = (pos as u32).into(); 47 let pos: TextSize = (pos as u32).into();
47 let off = token.text_range().start() + range.start() + pos; 48 let offset = token.text_range().start() + range.start() + pos;
48 if !edit.invalidates_offset(off) { 49 if !edit.invalidates_offset(offset) {
49 remove_newline(&mut edit, &token, off); 50 remove_newline(&mut edit, &token, offset);
50 } 51 }
51 } 52 }
52 } 53 }
@@ -56,14 +57,25 @@ pub(crate) fn join_lines(file: &SourceFile, range: TextRange) -> TextEdit {
56 57
57fn remove_newline(edit: &mut TextEditBuilder, token: &SyntaxToken, offset: TextSize) { 58fn remove_newline(edit: &mut TextEditBuilder, token: &SyntaxToken, offset: TextSize) {
58 if token.kind() != WHITESPACE || token.text().bytes().filter(|&b| b == b'\n').count() != 1 { 59 if token.kind() != WHITESPACE || token.text().bytes().filter(|&b| b == b'\n').count() != 1 {
59 // The node is either the first or the last in the file 60 let mut string_open_quote = false;
60 let suff = &token.text()[TextRange::new( 61 if let Some(string) = ast::String::cast(token.clone()) {
61 offset - token.text_range().start() + TextSize::of('\n'), 62 if let Some(range) = string.open_quote_text_range() {
62 TextSize::of(token.text()), 63 mark::hit!(join_string_literal);
63 )]; 64 string_open_quote = range.end() == offset;
64 let spaces = suff.bytes().take_while(|&b| b == b' ').count(); 65 }
65 66 }
66 edit.replace(TextRange::at(offset, ((spaces + 1) as u32).into()), " ".to_string()); 67
68 let n_spaces_after_line_break = {
69 let suff = &token.text()[TextRange::new(
70 offset - token.text_range().start() + TextSize::of('\n'),
71 TextSize::of(token.text()),
72 )];
73 suff.bytes().take_while(|&b| b == b' ').count()
74 };
75
76 let range = TextRange::at(offset, ((n_spaces_after_line_break + 1) as u32).into());
77 let replace_with = if string_open_quote { "" } else { " " };
78 edit.replace(range, replace_with.to_string());
67 return; 79 return;
68 } 80 }
69 81
@@ -194,7 +206,7 @@ fn compute_ws(left: SyntaxKind, right: SyntaxKind) -> &'static str {
194#[cfg(test)] 206#[cfg(test)]
195mod tests { 207mod tests {
196 use syntax::SourceFile; 208 use syntax::SourceFile;
197 use test_utils::{add_cursor, assert_eq_text, extract_offset, extract_range}; 209 use test_utils::{add_cursor, assert_eq_text, extract_offset, extract_range, mark};
198 210
199 use super::*; 211 use super::*;
200 212
@@ -771,4 +783,42 @@ fn foo() {
771 ", 783 ",
772 ); 784 );
773 } 785 }
786
787 #[test]
788 fn join_string_literal() {
789 mark::check!(join_string_literal);
790 check_join_lines(
791 r#"
792fn main() {
793 $0"
794hello
795";
796}
797"#,
798 r#"
799fn main() {
800 $0"hello
801";
802}
803"#,
804 );
805
806 check_join_lines(
807 r#"
808fn main() {
809 "
810$0hello
811world
812";
813}
814"#,
815 r#"
816fn main() {
817 "
818$0hello world
819";
820}
821"#,
822 );
823 }
774} 824}
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs
index 22ddeeae3..1919639a3 100644
--- a/crates/ide/src/references/rename.rs
+++ b/crates/ide/src/references/rename.rs
@@ -88,6 +88,8 @@ pub(crate) fn rename_with_semantics(
88 let def = find_definition(sema, syntax, position)?; 88 let def = find_definition(sema, syntax, position)?;
89 match def { 89 match def {
90 Definition::ModuleDef(ModuleDef::Module(module)) => rename_mod(&sema, module, new_name), 90 Definition::ModuleDef(ModuleDef::Module(module)) => rename_mod(&sema, module, new_name),
91 Definition::SelfType(_) => bail!("Cannot rename `Self`"),
92 Definition::ModuleDef(ModuleDef::BuiltinType(_)) => bail!("Cannot rename builtin type"),
91 def => rename_reference(sema, def, new_name), 93 def => rename_reference(sema, def, new_name),
92 } 94 }
93} 95}
@@ -122,7 +124,7 @@ fn check_identifier(new_name: &str) -> RenameResult<IdentifierKind> {
122 Ok(IdentifierKind::Lifetime) 124 Ok(IdentifierKind::Lifetime)
123 } 125 }
124 (SyntaxKind::LIFETIME_IDENT, _) => { 126 (SyntaxKind::LIFETIME_IDENT, _) => {
125 bail!("Invalid name `{0}`: Cannot rename lifetime to {0}", new_name) 127 bail!("Invalid name `{}`: not a lifetime identifier", new_name)
126 } 128 }
127 (_, Some(syntax_error)) => bail!("Invalid name `{}`: {}", new_name, syntax_error), 129 (_, Some(syntax_error)) => bail!("Invalid name `{}`: {}", new_name, syntax_error),
128 (_, None) => bail!("Invalid name `{}`: not an identifier", new_name), 130 (_, None) => bail!("Invalid name `{}`: not an identifier", new_name),
@@ -162,119 +164,6 @@ fn find_definition(
162 .ok_or_else(|| format_err!("No references found at position")) 164 .ok_or_else(|| format_err!("No references found at position"))
163} 165}
164 166
165fn source_edit_from_references(
166 _sema: &Semantics<RootDatabase>,
167 file_id: FileId,
168 references: &[FileReference],
169 def: Definition,
170 new_name: &str,
171) -> (FileId, TextEdit) {
172 let mut edit = TextEdit::builder();
173 for reference in references {
174 let (range, replacement) = match &reference.name {
175 // if the ranges differ then the node is inside a macro call, we can't really attempt
176 // to make special rewrites like shorthand syntax and such, so just rename the node in
177 // the macro input
178 ast::NameLike::NameRef(name_ref)
179 if name_ref.syntax().text_range() == reference.range =>
180 {
181 source_edit_from_name_ref(name_ref, new_name, def)
182 }
183 ast::NameLike::Name(name) if name.syntax().text_range() == reference.range => {
184 source_edit_from_name(name, new_name)
185 }
186 _ => None,
187 }
188 .unwrap_or_else(|| (reference.range, new_name.to_string()));
189 edit.replace(range, replacement);
190 }
191 (file_id, edit.finish())
192}
193
194fn source_edit_from_name(name: &ast::Name, new_name: &str) -> Option<(TextRange, String)> {
195 if let Some(_) = ast::RecordPatField::for_field_name(name) {
196 if let Some(ident_pat) = name.syntax().parent().and_then(ast::IdentPat::cast) {
197 return Some((
198 TextRange::empty(ident_pat.syntax().text_range().start()),
199 format!("{}: ", new_name),
200 ));
201 }
202 }
203 None
204}
205
206fn source_edit_from_name_ref(
207 name_ref: &ast::NameRef,
208 new_name: &str,
209 def: Definition,
210) -> Option<(TextRange, String)> {
211 if let Some(record_field) = ast::RecordExprField::for_name_ref(name_ref) {
212 let rcf_name_ref = record_field.name_ref();
213 let rcf_expr = record_field.expr();
214 match (rcf_name_ref, rcf_expr.and_then(|it| it.name_ref())) {
215 // field: init-expr, check if we can use a field init shorthand
216 (Some(field_name), Some(init)) => {
217 if field_name == *name_ref {
218 if init.text() == new_name {
219 mark::hit!(test_rename_field_put_init_shorthand);
220 // same names, we can use a shorthand here instead.
221 // we do not want to erase attributes hence this range start
222 let s = field_name.syntax().text_range().start();
223 let e = record_field.syntax().text_range().end();
224 return Some((TextRange::new(s, e), new_name.to_owned()));
225 }
226 } else if init == *name_ref {
227 if field_name.text() == new_name {
228 mark::hit!(test_rename_local_put_init_shorthand);
229 // same names, we can use a shorthand here instead.
230 // we do not want to erase attributes hence this range start
231 let s = field_name.syntax().text_range().start();
232 let e = record_field.syntax().text_range().end();
233 return Some((TextRange::new(s, e), new_name.to_owned()));
234 }
235 }
236 None
237 }
238 // init shorthand
239 // FIXME: instead of splitting the shorthand, recursively trigger a rename of the
240 // other name https://github.com/rust-analyzer/rust-analyzer/issues/6547
241 (None, Some(_)) if matches!(def, Definition::Field(_)) => {
242 mark::hit!(test_rename_field_in_field_shorthand);
243 let s = name_ref.syntax().text_range().start();
244 Some((TextRange::empty(s), format!("{}: ", new_name)))
245 }
246 (None, Some(_)) if matches!(def, Definition::Local(_)) => {
247 mark::hit!(test_rename_local_in_field_shorthand);
248 let s = name_ref.syntax().text_range().end();
249 Some((TextRange::empty(s), format!(": {}", new_name)))
250 }
251 _ => None,
252 }
253 } else if let Some(record_field) = ast::RecordPatField::for_field_name_ref(name_ref) {
254 let rcf_name_ref = record_field.name_ref();
255 let rcf_pat = record_field.pat();
256 match (rcf_name_ref, rcf_pat) {
257 // field: rename
258 (Some(field_name), Some(ast::Pat::IdentPat(pat))) if field_name == *name_ref => {
259 // field name is being renamed
260 if pat.name().map_or(false, |it| it.text() == new_name) {
261 mark::hit!(test_rename_field_put_init_shorthand_pat);
262 // same names, we can use a shorthand here instead/
263 // we do not want to erase attributes hence this range start
264 let s = field_name.syntax().text_range().start();
265 let e = record_field.syntax().text_range().end();
266 Some((TextRange::new(s, e), pat.to_string()))
267 } else {
268 None
269 }
270 }
271 _ => None,
272 }
273 } else {
274 None
275 }
276}
277
278fn rename_mod( 167fn rename_mod(
279 sema: &Semantics<RootDatabase>, 168 sema: &Semantics<RootDatabase>,
280 module: Module, 169 module: Module,
@@ -308,18 +197,75 @@ fn rename_mod(
308 TextEdit::replace(name.syntax().text_range(), new_name.to_string()), 197 TextEdit::replace(name.syntax().text_range(), new_name.to_string()),
309 ), 198 ),
310 _ => unreachable!(), 199 _ => unreachable!(),
311 }; 200 }
312 } 201 }
313 let def = Definition::ModuleDef(ModuleDef::Module(module)); 202 let def = Definition::ModuleDef(ModuleDef::Module(module));
314 let usages = def.usages(sema).all(); 203 let usages = def.usages(sema).all();
315 let ref_edits = usages.iter().map(|(&file_id, references)| { 204 let ref_edits = usages.iter().map(|(&file_id, references)| {
316 source_edit_from_references(sema, file_id, references, def, new_name) 205 (file_id, source_edit_from_references(references, def, new_name))
317 }); 206 });
318 source_change.extend(ref_edits); 207 source_change.extend(ref_edits);
319 208
320 Ok(source_change) 209 Ok(source_change)
321} 210}
322 211
212fn rename_reference(
213 sema: &Semantics<RootDatabase>,
214 def: Definition,
215 new_name: &str,
216) -> RenameResult<SourceChange> {
217 let ident_kind = check_identifier(new_name)?;
218
219 let def_is_lbl_or_lt = matches!(
220 def,
221 Definition::GenericParam(hir::GenericParam::LifetimeParam(_)) | Definition::Label(_)
222 );
223 match (ident_kind, def) {
224 (IdentifierKind::ToSelf, _)
225 | (IdentifierKind::Underscore, _)
226 | (IdentifierKind::Ident, _)
227 if def_is_lbl_or_lt =>
228 {
229 mark::hit!(rename_not_a_lifetime_ident_ref);
230 bail!("Invalid name `{}`: not a lifetime identifier", new_name)
231 }
232 (IdentifierKind::Lifetime, _) if def_is_lbl_or_lt => mark::hit!(rename_lifetime),
233 (IdentifierKind::Lifetime, _) => {
234 mark::hit!(rename_not_an_ident_ref);
235 bail!("Invalid name `{}`: not an identifier", new_name)
236 }
237 (IdentifierKind::ToSelf, Definition::Local(local)) if local.is_self(sema.db) => {
238 // no-op
239 mark::hit!(rename_self_to_self);
240 return Ok(SourceChange::default());
241 }
242 (ident_kind, Definition::Local(local)) if local.is_self(sema.db) => {
243 mark::hit!(rename_self_to_param);
244 return rename_self_to_param(sema, local, new_name, ident_kind);
245 }
246 (IdentifierKind::ToSelf, Definition::Local(local)) => {
247 mark::hit!(rename_to_self);
248 return rename_to_self(sema, local);
249 }
250 (IdentifierKind::ToSelf, _) => bail!("Invalid name `{}`: not an identifier", new_name),
251 (IdentifierKind::Ident, _) | (IdentifierKind::Underscore, _) => mark::hit!(rename_ident),
252 }
253
254 let usages = def.usages(sema).all();
255 if !usages.is_empty() && ident_kind == IdentifierKind::Underscore {
256 mark::hit!(rename_underscore_multiple);
257 bail!("Cannot rename reference to `_` as it is being referenced multiple times");
258 }
259 let mut source_change = SourceChange::default();
260 source_change.extend(usages.iter().map(|(&file_id, references)| {
261 (file_id, source_edit_from_references(&references, def, new_name))
262 }));
263
264 let (file_id, edit) = source_edit_from_def(sema, def, new_name)?;
265 source_change.insert_source_edit(file_id, edit);
266 Ok(source_change)
267}
268
323fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameResult<SourceChange> { 269fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameResult<SourceChange> {
324 if never!(local.is_self(sema.db)) { 270 if never!(local.is_self(sema.db)) {
325 bail!("rename_to_self invoked on self"); 271 bail!("rename_to_self invoked on self");
@@ -384,7 +330,7 @@ fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameRe
384 let usages = def.usages(sema).all(); 330 let usages = def.usages(sema).all();
385 let mut source_change = SourceChange::default(); 331 let mut source_change = SourceChange::default();
386 source_change.extend(usages.iter().map(|(&file_id, references)| { 332 source_change.extend(usages.iter().map(|(&file_id, references)| {
387 source_edit_from_references(sema, file_id, references, def, "self") 333 (file_id, source_edit_from_references(references, def, "self"))
388 })); 334 }));
389 source_change.insert_source_edit( 335 source_change.insert_source_edit(
390 file_id.original_file(sema.db), 336 file_id.original_file(sema.db),
@@ -394,29 +340,6 @@ fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameRe
394 Ok(source_change) 340 Ok(source_change)
395} 341}
396 342
397fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: &str) -> Option<TextEdit> {
398 fn target_type_name(impl_def: &ast::Impl) -> Option<String> {
399 if let Some(ast::Type::PathType(p)) = impl_def.self_ty() {
400 return Some(p.path()?.segment()?.name_ref()?.text().to_string());
401 }
402 None
403 }
404
405 let impl_def = self_param.syntax().ancestors().find_map(|it| ast::Impl::cast(it))?;
406 let type_name = target_type_name(&impl_def)?;
407
408 let mut replacement_text = String::from(new_name);
409 replacement_text.push_str(": ");
410 match (self_param.amp_token(), self_param.mut_token()) {
411 (None, None) => (),
412 (Some(_), None) => replacement_text.push('&'),
413 (_, Some(_)) => replacement_text.push_str("&mut "),
414 };
415 replacement_text.push_str(type_name.as_str());
416
417 Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text))
418}
419
420fn rename_self_to_param( 343fn rename_self_to_param(
421 sema: &Semantics<RootDatabase>, 344 sema: &Semantics<RootDatabase>,
422 local: hir::Local, 345 local: hir::Local,
@@ -441,66 +364,143 @@ fn rename_self_to_param(
441 let mut source_change = SourceChange::default(); 364 let mut source_change = SourceChange::default();
442 source_change.insert_source_edit(file_id.original_file(sema.db), edit); 365 source_change.insert_source_edit(file_id.original_file(sema.db), edit);
443 source_change.extend(usages.iter().map(|(&file_id, references)| { 366 source_change.extend(usages.iter().map(|(&file_id, references)| {
444 source_edit_from_references(sema, file_id, &references, def, new_name) 367 (file_id, source_edit_from_references(&references, def, new_name))
445 })); 368 }));
446 Ok(source_change) 369 Ok(source_change)
447} 370}
448 371
449fn rename_reference( 372fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: &str) -> Option<TextEdit> {
450 sema: &Semantics<RootDatabase>, 373 fn target_type_name(impl_def: &ast::Impl) -> Option<String> {
374 if let Some(ast::Type::PathType(p)) = impl_def.self_ty() {
375 return Some(p.path()?.segment()?.name_ref()?.text().to_string());
376 }
377 None
378 }
379
380 let impl_def = self_param.syntax().ancestors().find_map(|it| ast::Impl::cast(it))?;
381 let type_name = target_type_name(&impl_def)?;
382
383 let mut replacement_text = String::from(new_name);
384 replacement_text.push_str(": ");
385 match (self_param.amp_token(), self_param.mut_token()) {
386 (Some(_), None) => replacement_text.push('&'),
387 (Some(_), Some(_)) => replacement_text.push_str("&mut "),
388 (_, _) => (),
389 };
390 replacement_text.push_str(type_name.as_str());
391
392 Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text))
393}
394
395fn source_edit_from_references(
396 references: &[FileReference],
451 def: Definition, 397 def: Definition,
452 new_name: &str, 398 new_name: &str,
453) -> RenameResult<SourceChange> { 399) -> TextEdit {
454 let ident_kind = check_identifier(new_name)?; 400 let mut edit = TextEdit::builder();
455 401 for reference in references {
456 let def_is_lbl_or_lt = matches!( 402 let (range, replacement) = match &reference.name {
457 def, 403 // if the ranges differ then the node is inside a macro call, we can't really attempt
458 Definition::GenericParam(hir::GenericParam::LifetimeParam(_)) | Definition::Label(_) 404 // to make special rewrites like shorthand syntax and such, so just rename the node in
459 ); 405 // the macro input
460 match (ident_kind, def) { 406 ast::NameLike::NameRef(name_ref)
461 (IdentifierKind::ToSelf, _) 407 if name_ref.syntax().text_range() == reference.range =>
462 | (IdentifierKind::Underscore, _) 408 {
463 | (IdentifierKind::Ident, _) 409 source_edit_from_name_ref(name_ref, new_name, def)
464 if def_is_lbl_or_lt => 410 }
465 { 411 ast::NameLike::Name(name) if name.syntax().text_range() == reference.range => {
466 mark::hit!(rename_not_a_lifetime_ident_ref); 412 source_edit_from_name(name, new_name)
467 bail!("Invalid name `{}`: not a lifetime identifier", new_name) 413 }
468 } 414 _ => None,
469 (IdentifierKind::Lifetime, _) if def_is_lbl_or_lt => mark::hit!(rename_lifetime),
470 (IdentifierKind::Lifetime, _) => {
471 mark::hit!(rename_not_an_ident_ref);
472 bail!("Invalid name `{}`: not an identifier", new_name)
473 }
474 (IdentifierKind::ToSelf, Definition::Local(local)) if local.is_self(sema.db) => {
475 // no-op
476 mark::hit!(rename_self_to_self);
477 return Ok(SourceChange::default());
478 }
479 (ident_kind, Definition::Local(local)) if local.is_self(sema.db) => {
480 mark::hit!(rename_self_to_param);
481 return rename_self_to_param(sema, local, new_name, ident_kind);
482 }
483 (IdentifierKind::ToSelf, Definition::Local(local)) => {
484 mark::hit!(rename_to_self);
485 return rename_to_self(sema, local);
486 } 415 }
487 (IdentifierKind::ToSelf, _) => bail!("Invalid name `{}`: not an identifier", new_name), 416 .unwrap_or_else(|| (reference.range, new_name.to_string()));
488 (IdentifierKind::Ident, _) | (IdentifierKind::Underscore, _) => mark::hit!(rename_ident), 417 edit.replace(range, replacement);
489 } 418 }
419 edit.finish()
420}
490 421
491 let usages = def.usages(sema).all(); 422fn source_edit_from_name(name: &ast::Name, new_name: &str) -> Option<(TextRange, String)> {
492 if !usages.is_empty() && ident_kind == IdentifierKind::Underscore { 423 if let Some(_) = ast::RecordPatField::for_field_name(name) {
493 mark::hit!(rename_underscore_multiple); 424 if let Some(ident_pat) = name.syntax().parent().and_then(ast::IdentPat::cast) {
494 bail!("Cannot rename reference to `_` as it is being referenced multiple times"); 425 return Some((
426 TextRange::empty(ident_pat.syntax().text_range().start()),
427 [new_name, ": "].concat(),
428 ));
429 }
495 } 430 }
496 let mut source_change = SourceChange::default(); 431 None
497 source_change.extend(usages.iter().map(|(&file_id, references)| { 432}
498 source_edit_from_references(sema, file_id, &references, def, new_name)
499 }));
500 433
501 let (file_id, edit) = source_edit_from_def(sema, def, new_name)?; 434fn source_edit_from_name_ref(
502 source_change.insert_source_edit(file_id, edit); 435 name_ref: &ast::NameRef,
503 Ok(source_change) 436 new_name: &str,
437 def: Definition,
438) -> Option<(TextRange, String)> {
439 if let Some(record_field) = ast::RecordExprField::for_name_ref(name_ref) {
440 let rcf_name_ref = record_field.name_ref();
441 let rcf_expr = record_field.expr();
442 match (rcf_name_ref, rcf_expr.and_then(|it| it.name_ref())) {
443 // field: init-expr, check if we can use a field init shorthand
444 (Some(field_name), Some(init)) => {
445 if field_name == *name_ref {
446 if init.text() == new_name {
447 mark::hit!(test_rename_field_put_init_shorthand);
448 // same names, we can use a shorthand here instead.
449 // we do not want to erase attributes hence this range start
450 let s = field_name.syntax().text_range().start();
451 let e = record_field.syntax().text_range().end();
452 return Some((TextRange::new(s, e), new_name.to_owned()));
453 }
454 } else if init == *name_ref {
455 if field_name.text() == new_name {
456 mark::hit!(test_rename_local_put_init_shorthand);
457 // same names, we can use a shorthand here instead.
458 // we do not want to erase attributes hence this range start
459 let s = field_name.syntax().text_range().start();
460 let e = record_field.syntax().text_range().end();
461 return Some((TextRange::new(s, e), new_name.to_owned()));
462 }
463 }
464 None
465 }
466 // init shorthand
467 // FIXME: instead of splitting the shorthand, recursively trigger a rename of the
468 // other name https://github.com/rust-analyzer/rust-analyzer/issues/6547
469 (None, Some(_)) if matches!(def, Definition::Field(_)) => {
470 mark::hit!(test_rename_field_in_field_shorthand);
471 let s = name_ref.syntax().text_range().start();
472 Some((TextRange::empty(s), format!("{}: ", new_name)))
473 }
474 (None, Some(_)) if matches!(def, Definition::Local(_)) => {
475 mark::hit!(test_rename_local_in_field_shorthand);
476 let s = name_ref.syntax().text_range().end();
477 Some((TextRange::empty(s), format!(": {}", new_name)))
478 }
479 _ => None,
480 }
481 } else if let Some(record_field) = ast::RecordPatField::for_field_name_ref(name_ref) {
482 let rcf_name_ref = record_field.name_ref();
483 let rcf_pat = record_field.pat();
484 match (rcf_name_ref, rcf_pat) {
485 // field: rename
486 (Some(field_name), Some(ast::Pat::IdentPat(pat))) if field_name == *name_ref => {
487 // field name is being renamed
488 if pat.name().map_or(false, |it| it.text() == new_name) {
489 mark::hit!(test_rename_field_put_init_shorthand_pat);
490 // same names, we can use a shorthand here instead/
491 // we do not want to erase attributes hence this range start
492 let s = field_name.syntax().text_range().start();
493 let e = record_field.syntax().text_range().end();
494 Some((TextRange::new(s, e), pat.to_string()))
495 } else {
496 None
497 }
498 }
499 _ => None,
500 }
501 } else {
502 None
503 }
504} 504}
505 505
506fn source_edit_from_def( 506fn source_edit_from_def(
diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs
index 1e7baed20..65f60891e 100644
--- a/crates/ide/src/runnables.rs
+++ b/crates/ide/src/runnables.rs
@@ -189,7 +189,7 @@ pub(crate) fn doc_owner_to_def(
189) -> Option<Definition> { 189) -> Option<Definition> {
190 let res: hir::ModuleDef = match_ast! { 190 let res: hir::ModuleDef = match_ast! {
191 match item { 191 match item {
192 ast::SourceFile(it) => sema.scope(&item).module()?.into(), 192 ast::SourceFile(_it) => sema.scope(&item).module()?.into(),
193 ast::Fn(it) => sema.to_def(&it)?.into(), 193 ast::Fn(it) => sema.to_def(&it)?.into(),
194 ast::Struct(it) => sema.to_def(&it)?.into(), 194 ast::Struct(it) => sema.to_def(&it)?.into(),
195 ast::Enum(it) => sema.to_def(&it)?.into(), 195 ast::Enum(it) => sema.to_def(&it)?.into(),