diff options
Diffstat (limited to 'crates/ide')
-rw-r--r-- | crates/ide/src/folding_ranges.rs | 55 | ||||
-rw-r--r-- | crates/ide/src/hover.rs | 164 | ||||
-rw-r--r-- | crates/ide/src/join_lines.rs | 74 | ||||
-rw-r--r-- | crates/ide/src/references/rename.rs | 378 | ||||
-rw-r--r-- | crates/ide/src/runnables.rs | 2 |
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 | ||
455 | calling_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::{ | |||
5 | use ide_db::{ | 5 | use 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 | }; |
10 | use itertools::Itertools; | 11 | use 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 | ||
277 | fn 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 | |||
275 | fn definition_owner_name(db: &RootDatabase, def: &Definition) -> Option<String> { | 295 | fn 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 | ||
307 | fn hover_for_definition(db: &RootDatabase, def: Definition) -> Option<Markup> { | 327 | fn 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 | ||
409 | fn 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 | |||
433 | fn 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 | |||
441 | fn 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 | |||
383 | fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { | 450 | fn 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 | ||
3572 | mod foo { | ||
3573 | /// But this should appear | ||
3574 | pub mod bar {} | ||
3575 | } | ||
3576 | use 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 | ||
3599 | fn 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 | ||
3620 | cosnt _: &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 | }; |
10 | use test_utils::mark; | ||
10 | use text_edit::{TextEdit, TextEditBuilder}; | 11 | use 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 | ||
57 | fn remove_newline(edit: &mut TextEditBuilder, token: &SyntaxToken, offset: TextSize) { | 58 | fn 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)] |
195 | mod tests { | 207 | mod 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#" | ||
792 | fn main() { | ||
793 | $0" | ||
794 | hello | ||
795 | "; | ||
796 | } | ||
797 | "#, | ||
798 | r#" | ||
799 | fn main() { | ||
800 | $0"hello | ||
801 | "; | ||
802 | } | ||
803 | "#, | ||
804 | ); | ||
805 | |||
806 | check_join_lines( | ||
807 | r#" | ||
808 | fn main() { | ||
809 | " | ||
810 | $0hello | ||
811 | world | ||
812 | "; | ||
813 | } | ||
814 | "#, | ||
815 | r#" | ||
816 | fn 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 | ||
165 | fn 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 | |||
194 | fn 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 | |||
206 | fn 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 | |||
278 | fn rename_mod( | 167 | fn 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 | ||
212 | fn 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 | |||
323 | fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameResult<SourceChange> { | 269 | fn 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 | ||
397 | fn 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 | |||
420 | fn rename_self_to_param( | 343 | fn 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 | ||
449 | fn rename_reference( | 372 | fn 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 | |||
395 | fn 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(); | 422 | fn 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)?; | 434 | fn 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 | ||
506 | fn source_edit_from_def( | 506 | fn 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(), |