diff options
Diffstat (limited to 'crates')
-rw-r--r-- | crates/assists/src/handlers/extract_struct_from_enum_variant.rs | 59 | ||||
-rw-r--r-- | crates/assists/src/handlers/inline_local_variable.rs | 99 | ||||
-rw-r--r-- | crates/assists/src/handlers/remove_unused_param.rs | 41 | ||||
-rw-r--r-- | crates/assists/src/handlers/reorder_impl.rs | 201 | ||||
-rw-r--r-- | crates/assists/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/assists/src/tests/generated.rs | 35 | ||||
-rw-r--r-- | crates/ide/src/call_hierarchy.rs | 29 | ||||
-rw-r--r-- | crates/ide/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/ide/src/references.rs | 79 | ||||
-rw-r--r-- | crates/ide/src/references/rename.rs | 103 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting/highlights.rs | 14 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting/injector.rs | 4 | ||||
-rw-r--r-- | crates/ide_db/src/search.rs | 110 | ||||
-rw-r--r-- | crates/rust-analyzer/src/caps.rs | 24 | ||||
-rw-r--r-- | crates/rust-analyzer/src/handlers.rs | 69 | ||||
-rw-r--r-- | crates/rust-analyzer/src/to_proto.rs | 7 | ||||
-rw-r--r-- | crates/rust-analyzer/tests/rust-analyzer/main.rs | 144 | ||||
-rw-r--r-- | crates/ssr/src/search.rs | 20 |
18 files changed, 748 insertions, 294 deletions
diff --git a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs index 40028fc01..e3ef04932 100644 --- a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs +++ b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs | |||
@@ -2,12 +2,16 @@ use std::iter; | |||
2 | 2 | ||
3 | use either::Either; | 3 | use either::Either; |
4 | use hir::{AsName, Module, ModuleDef, Name, Variant}; | 4 | use hir::{AsName, Module, ModuleDef, Name, Variant}; |
5 | use ide_db::helpers::{ | 5 | use ide_db::{ |
6 | insert_use::{insert_use, ImportScope}, | 6 | defs::Definition, |
7 | mod_path_to_ast, | 7 | helpers::{ |
8 | insert_use::{insert_use, ImportScope}, | ||
9 | mod_path_to_ast, | ||
10 | }, | ||
11 | search::FileReference, | ||
12 | RootDatabase, | ||
8 | }; | 13 | }; |
9 | use ide_db::{defs::Definition, search::Reference, RootDatabase}; | 14 | use rustc_hash::FxHashSet; |
10 | use rustc_hash::{FxHashMap, FxHashSet}; | ||
11 | use syntax::{ | 15 | use syntax::{ |
12 | algo::{find_node_at_offset, SyntaxRewriter}, | 16 | algo::{find_node_at_offset, SyntaxRewriter}, |
13 | ast::{self, edit::IndentLevel, make, AstNode, NameOwner, VisibilityOwner}, | 17 | ast::{self, edit::IndentLevel, make, AstNode, NameOwner, VisibilityOwner}, |
@@ -58,29 +62,29 @@ pub(crate) fn extract_struct_from_enum_variant( | |||
58 | let mut visited_modules_set = FxHashSet::default(); | 62 | let mut visited_modules_set = FxHashSet::default(); |
59 | let current_module = enum_hir.module(ctx.db()); | 63 | let current_module = enum_hir.module(ctx.db()); |
60 | visited_modules_set.insert(current_module); | 64 | visited_modules_set.insert(current_module); |
61 | let mut rewriters = FxHashMap::default(); | 65 | let mut def_rewriter = None; |
62 | for reference in usages { | 66 | for (file_id, references) in usages { |
63 | let rewriter = rewriters | 67 | let mut rewriter = SyntaxRewriter::default(); |
64 | .entry(reference.file_range.file_id) | 68 | let source_file = ctx.sema.parse(file_id); |
65 | .or_insert_with(SyntaxRewriter::default); | 69 | for reference in references { |
66 | let source_file = ctx.sema.parse(reference.file_range.file_id); | 70 | update_reference( |
67 | update_reference( | 71 | ctx, |
68 | ctx, | 72 | &mut rewriter, |
69 | rewriter, | 73 | reference, |
70 | reference, | 74 | &source_file, |
71 | &source_file, | 75 | &enum_module_def, |
72 | &enum_module_def, | 76 | &variant_hir_name, |
73 | &variant_hir_name, | 77 | &mut visited_modules_set, |
74 | &mut visited_modules_set, | 78 | ); |
75 | ); | 79 | } |
76 | } | 80 | if file_id == ctx.frange.file_id { |
77 | let mut rewriter = | 81 | def_rewriter = Some(rewriter); |
78 | rewriters.remove(&ctx.frange.file_id).unwrap_or_else(SyntaxRewriter::default); | 82 | continue; |
79 | for (file_id, rewriter) in rewriters { | 83 | } |
80 | builder.edit_file(file_id); | 84 | builder.edit_file(file_id); |
81 | builder.rewrite(rewriter); | 85 | builder.rewrite(rewriter); |
82 | } | 86 | } |
83 | builder.edit_file(ctx.frange.file_id); | 87 | let mut rewriter = def_rewriter.unwrap_or_default(); |
84 | update_variant(&mut rewriter, &variant); | 88 | update_variant(&mut rewriter, &variant); |
85 | extract_struct_def( | 89 | extract_struct_def( |
86 | &mut rewriter, | 90 | &mut rewriter, |
@@ -90,6 +94,7 @@ pub(crate) fn extract_struct_from_enum_variant( | |||
90 | &variant.parent_enum().syntax().clone().into(), | 94 | &variant.parent_enum().syntax().clone().into(), |
91 | enum_ast.visibility(), | 95 | enum_ast.visibility(), |
92 | ); | 96 | ); |
97 | builder.edit_file(ctx.frange.file_id); | ||
93 | builder.rewrite(rewriter); | 98 | builder.rewrite(rewriter); |
94 | }, | 99 | }, |
95 | ) | 100 | ) |
@@ -205,13 +210,13 @@ fn update_variant(rewriter: &mut SyntaxRewriter, variant: &ast::Variant) -> Opti | |||
205 | fn update_reference( | 210 | fn update_reference( |
206 | ctx: &AssistContext, | 211 | ctx: &AssistContext, |
207 | rewriter: &mut SyntaxRewriter, | 212 | rewriter: &mut SyntaxRewriter, |
208 | reference: Reference, | 213 | reference: FileReference, |
209 | source_file: &SourceFile, | 214 | source_file: &SourceFile, |
210 | enum_module_def: &ModuleDef, | 215 | enum_module_def: &ModuleDef, |
211 | variant_hir_name: &Name, | 216 | variant_hir_name: &Name, |
212 | visited_modules_set: &mut FxHashSet<Module>, | 217 | visited_modules_set: &mut FxHashSet<Module>, |
213 | ) -> Option<()> { | 218 | ) -> Option<()> { |
214 | let offset = reference.file_range.range.start(); | 219 | let offset = reference.range.start(); |
215 | let (segment, expr) = if let Some(path_expr) = | 220 | let (segment, expr) = if let Some(path_expr) = |
216 | find_node_at_offset::<ast::PathExpr>(source_file.syntax(), offset) | 221 | find_node_at_offset::<ast::PathExpr>(source_file.syntax(), offset) |
217 | { | 222 | { |
diff --git a/crates/assists/src/handlers/inline_local_variable.rs b/crates/assists/src/handlers/inline_local_variable.rs index d559be9cb..dc798daaa 100644 --- a/crates/assists/src/handlers/inline_local_variable.rs +++ b/crates/assists/src/handlers/inline_local_variable.rs | |||
@@ -1,4 +1,7 @@ | |||
1 | use ide_db::{defs::Definition, search::ReferenceKind}; | 1 | use ide_db::{ |
2 | defs::Definition, | ||
3 | search::{FileReference, ReferenceKind}, | ||
4 | }; | ||
2 | use syntax::{ | 5 | use syntax::{ |
3 | ast::{self, AstNode, AstToken}, | 6 | ast::{self, AstNode, AstToken}, |
4 | TextRange, | 7 | TextRange, |
@@ -44,8 +47,8 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> O | |||
44 | 47 | ||
45 | let def = ctx.sema.to_def(&bind_pat)?; | 48 | let def = ctx.sema.to_def(&bind_pat)?; |
46 | let def = Definition::Local(def); | 49 | let def = Definition::Local(def); |
47 | let refs = def.usages(&ctx.sema).all(); | 50 | let usages = def.usages(&ctx.sema).all(); |
48 | if refs.is_empty() { | 51 | if usages.is_empty() { |
49 | mark::hit!(test_not_applicable_if_variable_unused); | 52 | mark::hit!(test_not_applicable_if_variable_unused); |
50 | return None; | 53 | return None; |
51 | }; | 54 | }; |
@@ -63,48 +66,45 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> O | |||
63 | let_stmt.syntax().text_range() | 66 | let_stmt.syntax().text_range() |
64 | }; | 67 | }; |
65 | 68 | ||
66 | let mut wrap_in_parens = vec![true; refs.len()]; | 69 | let wrap_in_parens = usages |
67 | 70 | .references | |
68 | for (i, desc) in refs.iter().enumerate() { | 71 | .values() |
69 | let usage_node = ctx | 72 | .flatten() |
70 | .covering_node_for_range(desc.file_range.range) | 73 | .map(|&FileReference { range, .. }| { |
71 | .ancestors() | 74 | let usage_node = |
72 | .find_map(ast::PathExpr::cast)?; | 75 | ctx.covering_node_for_range(range).ancestors().find_map(ast::PathExpr::cast)?; |
73 | let usage_parent_option = usage_node.syntax().parent().and_then(ast::Expr::cast); | 76 | let usage_parent_option = usage_node.syntax().parent().and_then(ast::Expr::cast); |
74 | let usage_parent = match usage_parent_option { | 77 | let usage_parent = match usage_parent_option { |
75 | Some(u) => u, | 78 | Some(u) => u, |
76 | None => { | 79 | None => return Ok(false), |
77 | wrap_in_parens[i] = false; | 80 | }; |
78 | continue; | 81 | |
79 | } | 82 | Ok(!matches!((&initializer_expr, usage_parent), |
80 | }; | 83 | (ast::Expr::CallExpr(_), _) |
81 | 84 | | (ast::Expr::IndexExpr(_), _) | |
82 | wrap_in_parens[i] = match (&initializer_expr, usage_parent) { | 85 | | (ast::Expr::MethodCallExpr(_), _) |
83 | (ast::Expr::CallExpr(_), _) | 86 | | (ast::Expr::FieldExpr(_), _) |
84 | | (ast::Expr::IndexExpr(_), _) | 87 | | (ast::Expr::TryExpr(_), _) |
85 | | (ast::Expr::MethodCallExpr(_), _) | 88 | | (ast::Expr::RefExpr(_), _) |
86 | | (ast::Expr::FieldExpr(_), _) | 89 | | (ast::Expr::Literal(_), _) |
87 | | (ast::Expr::TryExpr(_), _) | 90 | | (ast::Expr::TupleExpr(_), _) |
88 | | (ast::Expr::RefExpr(_), _) | 91 | | (ast::Expr::ArrayExpr(_), _) |
89 | | (ast::Expr::Literal(_), _) | 92 | | (ast::Expr::ParenExpr(_), _) |
90 | | (ast::Expr::TupleExpr(_), _) | 93 | | (ast::Expr::PathExpr(_), _) |
91 | | (ast::Expr::ArrayExpr(_), _) | 94 | | (ast::Expr::BlockExpr(_), _) |
92 | | (ast::Expr::ParenExpr(_), _) | 95 | | (ast::Expr::EffectExpr(_), _) |
93 | | (ast::Expr::PathExpr(_), _) | 96 | | (_, ast::Expr::CallExpr(_)) |
94 | | (ast::Expr::BlockExpr(_), _) | 97 | | (_, ast::Expr::TupleExpr(_)) |
95 | | (ast::Expr::EffectExpr(_), _) | 98 | | (_, ast::Expr::ArrayExpr(_)) |
96 | | (_, ast::Expr::CallExpr(_)) | 99 | | (_, ast::Expr::ParenExpr(_)) |
97 | | (_, ast::Expr::TupleExpr(_)) | 100 | | (_, ast::Expr::ForExpr(_)) |
98 | | (_, ast::Expr::ArrayExpr(_)) | 101 | | (_, ast::Expr::WhileExpr(_)) |
99 | | (_, ast::Expr::ParenExpr(_)) | 102 | | (_, ast::Expr::BreakExpr(_)) |
100 | | (_, ast::Expr::ForExpr(_)) | 103 | | (_, ast::Expr::ReturnExpr(_)) |
101 | | (_, ast::Expr::WhileExpr(_)) | 104 | | (_, ast::Expr::MatchExpr(_)) |
102 | | (_, ast::Expr::BreakExpr(_)) | 105 | )) |
103 | | (_, ast::Expr::ReturnExpr(_)) | 106 | }) |
104 | | (_, ast::Expr::MatchExpr(_)) => false, | 107 | .collect::<Result<Vec<_>, _>>()?; |
105 | _ => true, | ||
106 | }; | ||
107 | } | ||
108 | 108 | ||
109 | let init_str = initializer_expr.syntax().text().to_string(); | 109 | let init_str = initializer_expr.syntax().text().to_string(); |
110 | let init_in_paren = format!("({})", &init_str); | 110 | let init_in_paren = format!("({})", &init_str); |
@@ -116,15 +116,16 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> O | |||
116 | target, | 116 | target, |
117 | move |builder| { | 117 | move |builder| { |
118 | builder.delete(delete_range); | 118 | builder.delete(delete_range); |
119 | for (desc, should_wrap) in refs.iter().zip(wrap_in_parens) { | 119 | for (reference, should_wrap) in usages.references.values().flatten().zip(wrap_in_parens) |
120 | { | ||
120 | let replacement = | 121 | let replacement = |
121 | if should_wrap { init_in_paren.clone() } else { init_str.clone() }; | 122 | if should_wrap { init_in_paren.clone() } else { init_str.clone() }; |
122 | match desc.kind { | 123 | match reference.kind { |
123 | ReferenceKind::FieldShorthandForLocal => { | 124 | ReferenceKind::FieldShorthandForLocal => { |
124 | mark::hit!(inline_field_shorthand); | 125 | mark::hit!(inline_field_shorthand); |
125 | builder.insert(desc.file_range.range.end(), format!(": {}", replacement)) | 126 | builder.insert(reference.range.end(), format!(": {}", replacement)) |
126 | } | 127 | } |
127 | _ => builder.replace(desc.file_range.range, replacement), | 128 | _ => builder.replace(reference.range, replacement), |
128 | } | 129 | } |
129 | } | 130 | } |
130 | }, | 131 | }, |
diff --git a/crates/assists/src/handlers/remove_unused_param.rs b/crates/assists/src/handlers/remove_unused_param.rs index 56e8b5229..c961680e2 100644 --- a/crates/assists/src/handlers/remove_unused_param.rs +++ b/crates/assists/src/handlers/remove_unused_param.rs | |||
@@ -1,8 +1,8 @@ | |||
1 | use ide_db::{defs::Definition, search::Reference}; | 1 | use ide_db::{base_db::FileId, defs::Definition, search::FileReference}; |
2 | use syntax::{ | 2 | use syntax::{ |
3 | algo::find_node_at_range, | 3 | algo::find_node_at_range, |
4 | ast::{self, ArgListOwner}, | 4 | ast::{self, ArgListOwner}, |
5 | AstNode, SyntaxKind, SyntaxNode, TextRange, T, | 5 | AstNode, SourceFile, SyntaxKind, SyntaxNode, TextRange, T, |
6 | }; | 6 | }; |
7 | use test_utils::mark; | 7 | use test_utils::mark; |
8 | use SyntaxKind::WHITESPACE; | 8 | use SyntaxKind::WHITESPACE; |
@@ -58,32 +58,41 @@ pub(crate) fn remove_unused_param(acc: &mut Assists, ctx: &AssistContext) -> Opt | |||
58 | param.syntax().text_range(), | 58 | param.syntax().text_range(), |
59 | |builder| { | 59 | |builder| { |
60 | builder.delete(range_to_remove(param.syntax())); | 60 | builder.delete(range_to_remove(param.syntax())); |
61 | for usage in fn_def.usages(&ctx.sema).all() { | 61 | for (file_id, references) in fn_def.usages(&ctx.sema).all() { |
62 | process_usage(ctx, builder, usage, param_position); | 62 | process_usages(ctx, builder, file_id, references, param_position); |
63 | } | 63 | } |
64 | }, | 64 | }, |
65 | ) | 65 | ) |
66 | } | 66 | } |
67 | 67 | ||
68 | fn process_usage( | 68 | fn process_usages( |
69 | ctx: &AssistContext, | 69 | ctx: &AssistContext, |
70 | builder: &mut AssistBuilder, | 70 | builder: &mut AssistBuilder, |
71 | usage: Reference, | 71 | file_id: FileId, |
72 | references: Vec<FileReference>, | ||
72 | arg_to_remove: usize, | 73 | arg_to_remove: usize, |
73 | ) -> Option<()> { | 74 | ) { |
74 | let source_file = ctx.sema.parse(usage.file_range.file_id); | 75 | let source_file = ctx.sema.parse(file_id); |
75 | let call_expr: ast::CallExpr = | 76 | builder.edit_file(file_id); |
76 | find_node_at_range(source_file.syntax(), usage.file_range.range)?; | 77 | for usage in references { |
78 | if let Some(text_range) = process_usage(&source_file, usage, arg_to_remove) { | ||
79 | builder.delete(text_range); | ||
80 | } | ||
81 | } | ||
82 | } | ||
83 | |||
84 | fn process_usage( | ||
85 | source_file: &SourceFile, | ||
86 | FileReference { range, .. }: FileReference, | ||
87 | arg_to_remove: usize, | ||
88 | ) -> Option<TextRange> { | ||
89 | let call_expr: ast::CallExpr = find_node_at_range(source_file.syntax(), range)?; | ||
77 | let call_expr_range = call_expr.expr()?.syntax().text_range(); | 90 | let call_expr_range = call_expr.expr()?.syntax().text_range(); |
78 | if !call_expr_range.contains_range(usage.file_range.range) { | 91 | if !call_expr_range.contains_range(range) { |
79 | return None; | 92 | return None; |
80 | } | 93 | } |
81 | let arg = call_expr.arg_list()?.args().nth(arg_to_remove)?; | 94 | let arg = call_expr.arg_list()?.args().nth(arg_to_remove)?; |
82 | 95 | Some(range_to_remove(arg.syntax())) | |
83 | builder.edit_file(usage.file_range.file_id); | ||
84 | builder.delete(range_to_remove(arg.syntax())); | ||
85 | |||
86 | Some(()) | ||
87 | } | 96 | } |
88 | 97 | ||
89 | fn range_to_remove(node: &SyntaxNode) -> TextRange { | 98 | fn range_to_remove(node: &SyntaxNode) -> TextRange { |
diff --git a/crates/assists/src/handlers/reorder_impl.rs b/crates/assists/src/handlers/reorder_impl.rs new file mode 100644 index 000000000..309f291c8 --- /dev/null +++ b/crates/assists/src/handlers/reorder_impl.rs | |||
@@ -0,0 +1,201 @@ | |||
1 | use itertools::Itertools; | ||
2 | use rustc_hash::FxHashMap; | ||
3 | |||
4 | use hir::{PathResolution, Semantics}; | ||
5 | use ide_db::RootDatabase; | ||
6 | use syntax::{ | ||
7 | algo, | ||
8 | ast::{self, NameOwner}, | ||
9 | AstNode, | ||
10 | }; | ||
11 | use test_utils::mark; | ||
12 | |||
13 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | ||
14 | |||
15 | // Assist: reorder_impl | ||
16 | // | ||
17 | // Reorder the methods of an `impl Trait`. The methods will be ordered | ||
18 | // in the same order as in the trait definition. | ||
19 | // | ||
20 | // ``` | ||
21 | // trait Foo { | ||
22 | // fn a() {} | ||
23 | // fn b() {} | ||
24 | // fn c() {} | ||
25 | // } | ||
26 | // | ||
27 | // struct Bar; | ||
28 | // $0impl Foo for Bar { | ||
29 | // fn b() {} | ||
30 | // fn c() {} | ||
31 | // fn a() {} | ||
32 | // } | ||
33 | // ``` | ||
34 | // -> | ||
35 | // ``` | ||
36 | // trait Foo { | ||
37 | // fn a() {} | ||
38 | // fn b() {} | ||
39 | // fn c() {} | ||
40 | // } | ||
41 | // | ||
42 | // struct Bar; | ||
43 | // impl Foo for Bar { | ||
44 | // fn a() {} | ||
45 | // fn b() {} | ||
46 | // fn c() {} | ||
47 | // } | ||
48 | // ``` | ||
49 | // | ||
50 | pub(crate) fn reorder_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
51 | let impl_ast = ctx.find_node_at_offset::<ast::Impl>()?; | ||
52 | let items = impl_ast.assoc_item_list()?; | ||
53 | let methods = get_methods(&items); | ||
54 | |||
55 | let path = impl_ast | ||
56 | .trait_() | ||
57 | .and_then(|t| match t { | ||
58 | ast::Type::PathType(path) => Some(path), | ||
59 | _ => None, | ||
60 | })? | ||
61 | .path()?; | ||
62 | |||
63 | let ranks = compute_method_ranks(&path, ctx)?; | ||
64 | let sorted: Vec<_> = methods | ||
65 | .iter() | ||
66 | .cloned() | ||
67 | .sorted_by_key(|f| { | ||
68 | f.name().and_then(|n| ranks.get(&n.to_string()).copied()).unwrap_or(usize::max_value()) | ||
69 | }) | ||
70 | .collect(); | ||
71 | |||
72 | // Don't edit already sorted methods: | ||
73 | if methods == sorted { | ||
74 | mark::hit!(not_applicable_if_sorted); | ||
75 | return None; | ||
76 | } | ||
77 | |||
78 | let target = items.syntax().text_range(); | ||
79 | acc.add(AssistId("reorder_impl", AssistKind::RefactorRewrite), "Sort methods", target, |edit| { | ||
80 | let mut rewriter = algo::SyntaxRewriter::default(); | ||
81 | for (old, new) in methods.iter().zip(&sorted) { | ||
82 | rewriter.replace(old.syntax(), new.syntax()); | ||
83 | } | ||
84 | edit.rewrite(rewriter); | ||
85 | }) | ||
86 | } | ||
87 | |||
88 | fn compute_method_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> { | ||
89 | let td = trait_definition(path, &ctx.sema)?; | ||
90 | |||
91 | Some( | ||
92 | td.items(ctx.db()) | ||
93 | .iter() | ||
94 | .flat_map(|i| match i { | ||
95 | hir::AssocItem::Function(f) => Some(f), | ||
96 | _ => None, | ||
97 | }) | ||
98 | .enumerate() | ||
99 | .map(|(idx, func)| ((func.name(ctx.db()).to_string(), idx))) | ||
100 | .collect(), | ||
101 | ) | ||
102 | } | ||
103 | |||
104 | fn trait_definition(path: &ast::Path, sema: &Semantics<RootDatabase>) -> Option<hir::Trait> { | ||
105 | match sema.resolve_path(path)? { | ||
106 | PathResolution::Def(hir::ModuleDef::Trait(trait_)) => Some(trait_), | ||
107 | _ => None, | ||
108 | } | ||
109 | } | ||
110 | |||
111 | fn get_methods(items: &ast::AssocItemList) -> Vec<ast::Fn> { | ||
112 | items | ||
113 | .assoc_items() | ||
114 | .flat_map(|i| match i { | ||
115 | ast::AssocItem::Fn(f) => Some(f), | ||
116 | _ => None, | ||
117 | }) | ||
118 | .filter(|f| f.name().is_some()) | ||
119 | .collect() | ||
120 | } | ||
121 | |||
122 | #[cfg(test)] | ||
123 | mod tests { | ||
124 | use test_utils::mark; | ||
125 | |||
126 | use crate::tests::{check_assist, check_assist_not_applicable}; | ||
127 | |||
128 | use super::*; | ||
129 | |||
130 | #[test] | ||
131 | fn not_applicable_if_sorted() { | ||
132 | mark::check!(not_applicable_if_sorted); | ||
133 | check_assist_not_applicable( | ||
134 | reorder_impl, | ||
135 | r#" | ||
136 | trait Bar { | ||
137 | fn a() {} | ||
138 | fn z() {} | ||
139 | fn b() {} | ||
140 | } | ||
141 | struct Foo; | ||
142 | $0impl Bar for Foo { | ||
143 | fn a() {} | ||
144 | fn z() {} | ||
145 | fn b() {} | ||
146 | } | ||
147 | "#, | ||
148 | ) | ||
149 | } | ||
150 | |||
151 | #[test] | ||
152 | fn not_applicable_if_empty() { | ||
153 | check_assist_not_applicable( | ||
154 | reorder_impl, | ||
155 | r#" | ||
156 | trait Bar {}; | ||
157 | struct Foo; | ||
158 | $0impl Bar for Foo {} | ||
159 | "#, | ||
160 | ) | ||
161 | } | ||
162 | |||
163 | #[test] | ||
164 | fn reorder_impl_trait_methods() { | ||
165 | check_assist( | ||
166 | reorder_impl, | ||
167 | r#" | ||
168 | trait Bar { | ||
169 | fn a() {} | ||
170 | fn c() {} | ||
171 | fn b() {} | ||
172 | fn d() {} | ||
173 | } | ||
174 | |||
175 | struct Foo; | ||
176 | $0impl Bar for Foo { | ||
177 | fn d() {} | ||
178 | fn b() {} | ||
179 | fn c() {} | ||
180 | fn a() {} | ||
181 | } | ||
182 | "#, | ||
183 | r#" | ||
184 | trait Bar { | ||
185 | fn a() {} | ||
186 | fn c() {} | ||
187 | fn b() {} | ||
188 | fn d() {} | ||
189 | } | ||
190 | |||
191 | struct Foo; | ||
192 | impl Bar for Foo { | ||
193 | fn a() {} | ||
194 | fn c() {} | ||
195 | fn b() {} | ||
196 | fn d() {} | ||
197 | } | ||
198 | "#, | ||
199 | ) | ||
200 | } | ||
201 | } | ||
diff --git a/crates/assists/src/lib.rs b/crates/assists/src/lib.rs index 90009c55a..1080294ab 100644 --- a/crates/assists/src/lib.rs +++ b/crates/assists/src/lib.rs | |||
@@ -146,6 +146,7 @@ mod handlers { | |||
146 | mod remove_mut; | 146 | mod remove_mut; |
147 | mod remove_unused_param; | 147 | mod remove_unused_param; |
148 | mod reorder_fields; | 148 | mod reorder_fields; |
149 | mod reorder_impl; | ||
149 | mod replace_derive_with_manual_impl; | 150 | mod replace_derive_with_manual_impl; |
150 | mod replace_if_let_with_match; | 151 | mod replace_if_let_with_match; |
151 | mod replace_impl_trait_with_generic; | 152 | mod replace_impl_trait_with_generic; |
@@ -202,6 +203,7 @@ mod handlers { | |||
202 | remove_mut::remove_mut, | 203 | remove_mut::remove_mut, |
203 | remove_unused_param::remove_unused_param, | 204 | remove_unused_param::remove_unused_param, |
204 | reorder_fields::reorder_fields, | 205 | reorder_fields::reorder_fields, |
206 | reorder_impl::reorder_impl, | ||
205 | replace_derive_with_manual_impl::replace_derive_with_manual_impl, | 207 | replace_derive_with_manual_impl::replace_derive_with_manual_impl, |
206 | replace_if_let_with_match::replace_if_let_with_match, | 208 | replace_if_let_with_match::replace_if_let_with_match, |
207 | replace_if_let_with_match::replace_match_with_if_let, | 209 | replace_if_let_with_match::replace_match_with_if_let, |
diff --git a/crates/assists/src/tests/generated.rs b/crates/assists/src/tests/generated.rs index e28837b53..217f577eb 100644 --- a/crates/assists/src/tests/generated.rs +++ b/crates/assists/src/tests/generated.rs | |||
@@ -896,6 +896,41 @@ const test: Foo = Foo {foo: 1, bar: 0} | |||
896 | } | 896 | } |
897 | 897 | ||
898 | #[test] | 898 | #[test] |
899 | fn doctest_reorder_impl() { | ||
900 | check_doc_test( | ||
901 | "reorder_impl", | ||
902 | r#####" | ||
903 | trait Foo { | ||
904 | fn a() {} | ||
905 | fn b() {} | ||
906 | fn c() {} | ||
907 | } | ||
908 | |||
909 | struct Bar; | ||
910 | $0impl Foo for Bar { | ||
911 | fn b() {} | ||
912 | fn c() {} | ||
913 | fn a() {} | ||
914 | } | ||
915 | "#####, | ||
916 | r#####" | ||
917 | trait Foo { | ||
918 | fn a() {} | ||
919 | fn b() {} | ||
920 | fn c() {} | ||
921 | } | ||
922 | |||
923 | struct Bar; | ||
924 | impl Foo for Bar { | ||
925 | fn a() {} | ||
926 | fn b() {} | ||
927 | fn c() {} | ||
928 | } | ||
929 | "#####, | ||
930 | ) | ||
931 | } | ||
932 | |||
933 | #[test] | ||
899 | fn doctest_replace_derive_with_manual_impl() { | 934 | fn doctest_replace_derive_with_manual_impl() { |
900 | check_doc_test( | 935 | check_doc_test( |
901 | "replace_derive_with_manual_impl", | 936 | "replace_derive_with_manual_impl", |
diff --git a/crates/ide/src/call_hierarchy.rs b/crates/ide/src/call_hierarchy.rs index b29d1fef9..e8999a7f3 100644 --- a/crates/ide/src/call_hierarchy.rs +++ b/crates/ide/src/call_hierarchy.rs | |||
@@ -47,22 +47,23 @@ pub(crate) fn incoming_calls(db: &RootDatabase, position: FilePosition) -> Optio | |||
47 | 47 | ||
48 | let mut calls = CallLocations::default(); | 48 | let mut calls = CallLocations::default(); |
49 | 49 | ||
50 | for reference in refs.info.references() { | 50 | for (&file_id, references) in refs.info.references().iter() { |
51 | let file_id = reference.file_range.file_id; | ||
52 | let file = sema.parse(file_id); | 51 | let file = sema.parse(file_id); |
53 | let file = file.syntax(); | 52 | let file = file.syntax(); |
54 | let token = file.token_at_offset(reference.file_range.range.start()).next()?; | 53 | for reference in references { |
55 | let token = sema.descend_into_macros(token); | 54 | let token = file.token_at_offset(reference.range.start()).next()?; |
56 | let syntax = token.parent(); | 55 | let token = sema.descend_into_macros(token); |
57 | 56 | let syntax = token.parent(); | |
58 | // This target is the containing function | 57 | |
59 | if let Some(nav) = syntax.ancestors().find_map(|node| { | 58 | // This target is the containing function |
60 | let fn_ = ast::Fn::cast(node)?; | 59 | if let Some(nav) = syntax.ancestors().find_map(|node| { |
61 | let def = sema.to_def(&fn_)?; | 60 | let fn_ = ast::Fn::cast(node)?; |
62 | def.try_to_nav(sema.db) | 61 | let def = sema.to_def(&fn_)?; |
63 | }) { | 62 | def.try_to_nav(sema.db) |
64 | let relative_range = reference.file_range.range; | 63 | }) { |
65 | calls.add(&nav, relative_range); | 64 | let relative_range = reference.range; |
65 | calls.add(&nav, relative_range); | ||
66 | } | ||
66 | } | 67 | } |
67 | } | 68 | } |
68 | 69 | ||
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 1f368cbd0..1e03832ec 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs | |||
@@ -92,7 +92,7 @@ pub use ide_db::base_db::{ | |||
92 | }; | 92 | }; |
93 | pub use ide_db::{ | 93 | pub use ide_db::{ |
94 | call_info::CallInfo, | 94 | call_info::CallInfo, |
95 | search::{Reference, ReferenceAccess, ReferenceKind}, | 95 | search::{FileReference, ReferenceAccess, ReferenceKind}, |
96 | }; | 96 | }; |
97 | pub use ide_db::{ | 97 | pub use ide_db::{ |
98 | label::Label, | 98 | label::Label, |
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs index b774a2be1..7d4757e02 100644 --- a/crates/ide/src/references.rs +++ b/crates/ide/src/references.rs | |||
@@ -13,9 +13,9 @@ pub(crate) mod rename; | |||
13 | 13 | ||
14 | use hir::Semantics; | 14 | use hir::Semantics; |
15 | use ide_db::{ | 15 | use ide_db::{ |
16 | base_db::FileId, | ||
16 | defs::{Definition, NameClass, NameRefClass}, | 17 | defs::{Definition, NameClass, NameRefClass}, |
17 | search::Reference, | 18 | search::{FileReference, ReferenceAccess, ReferenceKind, SearchScope, UsageSearchResult}, |
18 | search::{ReferenceAccess, ReferenceKind, SearchScope}, | ||
19 | RootDatabase, | 19 | RootDatabase, |
20 | }; | 20 | }; |
21 | use syntax::{ | 21 | use syntax::{ |
@@ -29,7 +29,7 @@ use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeI | |||
29 | #[derive(Debug, Clone)] | 29 | #[derive(Debug, Clone)] |
30 | pub struct ReferenceSearchResult { | 30 | pub struct ReferenceSearchResult { |
31 | declaration: Declaration, | 31 | declaration: Declaration, |
32 | references: Vec<Reference>, | 32 | references: UsageSearchResult, |
33 | } | 33 | } |
34 | 34 | ||
35 | #[derive(Debug, Clone)] | 35 | #[derive(Debug, Clone)] |
@@ -48,10 +48,21 @@ impl ReferenceSearchResult { | |||
48 | &self.declaration.nav | 48 | &self.declaration.nav |
49 | } | 49 | } |
50 | 50 | ||
51 | pub fn references(&self) -> &[Reference] { | 51 | pub fn references(&self) -> &UsageSearchResult { |
52 | &self.references | 52 | &self.references |
53 | } | 53 | } |
54 | 54 | ||
55 | pub fn references_with_declaration(mut self) -> UsageSearchResult { | ||
56 | let decl_ref = FileReference { | ||
57 | range: self.declaration.nav.focus_or_full_range(), | ||
58 | kind: self.declaration.kind, | ||
59 | access: self.declaration.access, | ||
60 | }; | ||
61 | let file_id = self.declaration.nav.file_id; | ||
62 | self.references.references.entry(file_id).or_default().push(decl_ref); | ||
63 | self.references | ||
64 | } | ||
65 | |||
55 | /// Total number of references | 66 | /// Total number of references |
56 | /// At least 1 since all valid references should | 67 | /// At least 1 since all valid references should |
57 | /// Have a declaration | 68 | /// Have a declaration |
@@ -63,21 +74,11 @@ impl ReferenceSearchResult { | |||
63 | // allow turning ReferenceSearchResult into an iterator | 74 | // allow turning ReferenceSearchResult into an iterator |
64 | // over References | 75 | // over References |
65 | impl IntoIterator for ReferenceSearchResult { | 76 | impl IntoIterator for ReferenceSearchResult { |
66 | type Item = Reference; | 77 | type Item = (FileId, Vec<FileReference>); |
67 | type IntoIter = std::vec::IntoIter<Reference>; | 78 | type IntoIter = std::collections::hash_map::IntoIter<FileId, Vec<FileReference>>; |
68 | 79 | ||
69 | fn into_iter(mut self) -> Self::IntoIter { | 80 | fn into_iter(self) -> Self::IntoIter { |
70 | let mut v = Vec::with_capacity(self.len()); | 81 | self.references_with_declaration().into_iter() |
71 | v.push(Reference { | ||
72 | file_range: FileRange { | ||
73 | file_id: self.declaration.nav.file_id, | ||
74 | range: self.declaration.nav.focus_or_full_range(), | ||
75 | }, | ||
76 | kind: self.declaration.kind, | ||
77 | access: self.declaration.access, | ||
78 | }); | ||
79 | v.append(&mut self.references); | ||
80 | v.into_iter() | ||
81 | } | 82 | } |
82 | } | 83 | } |
83 | 84 | ||
@@ -109,13 +110,12 @@ pub(crate) fn find_all_refs( | |||
109 | 110 | ||
110 | let RangeInfo { range, info: def } = find_name(&sema, &syntax, position, opt_name)?; | 111 | let RangeInfo { range, info: def } = find_name(&sema, &syntax, position, opt_name)?; |
111 | 112 | ||
112 | let references = def | 113 | let mut usages = def.usages(sema).set_scope(search_scope).all(); |
113 | .usages(sema) | 114 | usages |
114 | .set_scope(search_scope) | 115 | .references |
115 | .all() | 116 | .values_mut() |
116 | .into_iter() | 117 | .for_each(|it| it.retain(|r| search_kind == ReferenceKind::Other || search_kind == r.kind)); |
117 | .filter(|r| search_kind == ReferenceKind::Other || search_kind == r.kind) | 118 | usages.references.retain(|_, it| !it.is_empty()); |
118 | .collect(); | ||
119 | 119 | ||
120 | let nav = def.try_to_nav(sema.db)?; | 120 | let nav = def.try_to_nav(sema.db)?; |
121 | let decl_range = nav.focus_or_full_range(); | 121 | let decl_range = nav.focus_or_full_range(); |
@@ -139,7 +139,7 @@ pub(crate) fn find_all_refs( | |||
139 | 139 | ||
140 | let declaration = Declaration { nav, kind, access: decl_access(&def, &syntax, decl_range) }; | 140 | let declaration = Declaration { nav, kind, access: decl_access(&def, &syntax, decl_range) }; |
141 | 141 | ||
142 | Some(RangeInfo::new(range, ReferenceSearchResult { declaration, references })) | 142 | Some(RangeInfo::new(range, ReferenceSearchResult { declaration, references: usages })) |
143 | } | 143 | } |
144 | 144 | ||
145 | fn find_name( | 145 | fn find_name( |
@@ -255,7 +255,8 @@ fn try_find_self_references( | |||
255 | syntax: &SyntaxNode, | 255 | syntax: &SyntaxNode, |
256 | position: FilePosition, | 256 | position: FilePosition, |
257 | ) -> Option<RangeInfo<ReferenceSearchResult>> { | 257 | ) -> Option<RangeInfo<ReferenceSearchResult>> { |
258 | let self_token = syntax.token_at_offset(position.offset).find(|t| t.kind() == T![self])?; | 258 | let FilePosition { file_id, offset } = position; |
259 | let self_token = syntax.token_at_offset(offset).find(|t| t.kind() == T![self])?; | ||
259 | let parent = self_token.parent(); | 260 | let parent = self_token.parent(); |
260 | match_ast! { | 261 | match_ast! { |
261 | match parent { | 262 | match parent { |
@@ -276,7 +277,7 @@ fn try_find_self_references( | |||
276 | 277 | ||
277 | let declaration = Declaration { | 278 | let declaration = Declaration { |
278 | nav: NavigationTarget { | 279 | nav: NavigationTarget { |
279 | file_id: position.file_id, | 280 | file_id, |
280 | full_range: self_param.syntax().text_range(), | 281 | full_range: self_param.syntax().text_range(), |
281 | focus_range: Some(param_self_token.text_range()), | 282 | focus_range: Some(param_self_token.text_range()), |
282 | name: param_self_token.text().clone(), | 283 | name: param_self_token.text().clone(), |
@@ -292,7 +293,7 @@ fn try_find_self_references( | |||
292 | ReferenceAccess::Read | 293 | ReferenceAccess::Read |
293 | }), | 294 | }), |
294 | }; | 295 | }; |
295 | let references = function | 296 | let refs = function |
296 | .body() | 297 | .body() |
297 | .map(|body| { | 298 | .map(|body| { |
298 | body.syntax() | 299 | body.syntax() |
@@ -306,14 +307,16 @@ fn try_find_self_references( | |||
306 | None | 307 | None |
307 | } | 308 | } |
308 | }) | 309 | }) |
309 | .map(|token| Reference { | 310 | .map(|token| FileReference { |
310 | file_range: FileRange { file_id: position.file_id, range: token.text_range() }, | 311 | range: token.text_range(), |
311 | kind: ReferenceKind::SelfKw, | 312 | kind: ReferenceKind::SelfKw, |
312 | access: declaration.access, // FIXME: properly check access kind here instead of copying it from the declaration | 313 | access: declaration.access, // FIXME: properly check access kind here instead of copying it from the declaration |
313 | }) | 314 | }) |
314 | .collect() | 315 | .collect() |
315 | }) | 316 | }) |
316 | .unwrap_or_default(); | 317 | .unwrap_or_default(); |
318 | let mut references = UsageSearchResult::default(); | ||
319 | references.references.insert(file_id, refs); | ||
317 | 320 | ||
318 | Some(RangeInfo::new( | 321 | Some(RangeInfo::new( |
319 | param_self_token.text_range(), | 322 | param_self_token.text_range(), |
@@ -1018,12 +1021,14 @@ impl Foo { | |||
1018 | actual += "\n\n"; | 1021 | actual += "\n\n"; |
1019 | } | 1022 | } |
1020 | 1023 | ||
1021 | for r in &refs.references { | 1024 | for (file_id, references) in refs.references { |
1022 | format_to!(actual, "{:?} {:?} {:?}", r.file_range.file_id, r.file_range.range, r.kind); | 1025 | for r in references { |
1023 | if let Some(access) = r.access { | 1026 | format_to!(actual, "{:?} {:?} {:?}", file_id, r.range, r.kind); |
1024 | format_to!(actual, " {:?}", access); | 1027 | if let Some(access) = r.access { |
1028 | format_to!(actual, " {:?}", access); | ||
1029 | } | ||
1030 | actual += "\n"; | ||
1025 | } | 1031 | } |
1026 | actual += "\n"; | ||
1027 | } | 1032 | } |
1028 | expect.assert_eq(&actual) | 1033 | expect.assert_eq(&actual) |
1029 | } | 1034 | } |
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs index 3edc43e08..c3ae568c2 100644 --- a/crates/ide/src/references/rename.rs +++ b/crates/ide/src/references/rename.rs | |||
@@ -1,14 +1,14 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! FIXME: write short doc here |
2 | use std::{ | 2 | use std::{ |
3 | convert::TryInto, | 3 | convert::TryInto, |
4 | error::Error, | ||
5 | fmt::{self, Display}, | 4 | fmt::{self, Display}, |
6 | }; | 5 | }; |
7 | 6 | ||
8 | use hir::{Module, ModuleDef, ModuleSource, Semantics}; | 7 | use hir::{Module, ModuleDef, ModuleSource, Semantics}; |
9 | use ide_db::base_db::{AnchoredPathBuf, FileId, FileRange, SourceDatabaseExt}; | ||
10 | use ide_db::{ | 8 | use ide_db::{ |
9 | base_db::{AnchoredPathBuf, FileId, FileRange, SourceDatabaseExt}, | ||
11 | defs::{Definition, NameClass, NameRefClass}, | 10 | defs::{Definition, NameClass, NameRefClass}, |
11 | search::FileReference, | ||
12 | RootDatabase, | 12 | RootDatabase, |
13 | }; | 13 | }; |
14 | use syntax::{ | 14 | use syntax::{ |
@@ -20,8 +20,8 @@ use test_utils::mark; | |||
20 | use text_edit::TextEdit; | 20 | use text_edit::TextEdit; |
21 | 21 | ||
22 | use crate::{ | 22 | use crate::{ |
23 | FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind, ReferenceSearchResult, | 23 | FilePosition, FileSystemEdit, RangeInfo, ReferenceKind, ReferenceSearchResult, SourceChange, |
24 | SourceChange, SourceFileEdit, TextRange, TextSize, | 24 | SourceFileEdit, TextRange, TextSize, |
25 | }; | 25 | }; |
26 | 26 | ||
27 | type RenameResult<T> = Result<T, RenameError>; | 27 | type RenameResult<T> = Result<T, RenameError>; |
@@ -34,8 +34,6 @@ impl fmt::Display for RenameError { | |||
34 | } | 34 | } |
35 | } | 35 | } |
36 | 36 | ||
37 | impl Error for RenameError {} | ||
38 | |||
39 | macro_rules! format_err { | 37 | macro_rules! format_err { |
40 | ($fmt:expr) => {RenameError(format!($fmt))}; | 38 | ($fmt:expr) => {RenameError(format!($fmt))}; |
41 | ($fmt:expr, $($arg:tt)+) => {RenameError(format!($fmt, $($arg)+))} | 39 | ($fmt:expr, $($arg:tt)+) => {RenameError(format!($fmt, $($arg)+))} |
@@ -173,39 +171,46 @@ fn find_all_refs( | |||
173 | .ok_or_else(|| format_err!("No references found at position")) | 171 | .ok_or_else(|| format_err!("No references found at position")) |
174 | } | 172 | } |
175 | 173 | ||
176 | fn source_edit_from_reference( | 174 | fn source_edit_from_references( |
177 | sema: &Semantics<RootDatabase>, | 175 | sema: &Semantics<RootDatabase>, |
178 | reference: Reference, | 176 | file_id: FileId, |
177 | references: &[FileReference], | ||
179 | new_name: &str, | 178 | new_name: &str, |
180 | ) -> SourceFileEdit { | 179 | ) -> SourceFileEdit { |
181 | let mut replacement_text = String::new(); | 180 | let mut edit = TextEdit::builder(); |
182 | let range = match reference.kind { | 181 | for reference in references { |
183 | ReferenceKind::FieldShorthandForField => { | 182 | let mut replacement_text = String::new(); |
184 | mark::hit!(test_rename_struct_field_for_shorthand); | 183 | let range = match reference.kind { |
185 | replacement_text.push_str(new_name); | 184 | ReferenceKind::FieldShorthandForField => { |
186 | replacement_text.push_str(": "); | 185 | mark::hit!(test_rename_struct_field_for_shorthand); |
187 | TextRange::new(reference.file_range.range.start(), reference.file_range.range.start()) | 186 | replacement_text.push_str(new_name); |
188 | } | 187 | replacement_text.push_str(": "); |
189 | ReferenceKind::FieldShorthandForLocal => { | 188 | TextRange::new(reference.range.start(), reference.range.start()) |
190 | mark::hit!(test_rename_local_for_field_shorthand); | 189 | } |
191 | replacement_text.push_str(": "); | 190 | ReferenceKind::FieldShorthandForLocal => { |
192 | replacement_text.push_str(new_name); | 191 | mark::hit!(test_rename_local_for_field_shorthand); |
193 | TextRange::new(reference.file_range.range.end(), reference.file_range.range.end()) | 192 | replacement_text.push_str(": "); |
194 | } | 193 | replacement_text.push_str(new_name); |
195 | ReferenceKind::RecordFieldExprOrPat => { | 194 | TextRange::new(reference.range.end(), reference.range.end()) |
196 | mark::hit!(test_rename_field_expr_pat); | 195 | } |
197 | replacement_text.push_str(new_name); | 196 | ReferenceKind::RecordFieldExprOrPat => { |
198 | edit_text_range_for_record_field_expr_or_pat(sema, reference.file_range, new_name) | 197 | mark::hit!(test_rename_field_expr_pat); |
199 | } | 198 | replacement_text.push_str(new_name); |
200 | _ => { | 199 | edit_text_range_for_record_field_expr_or_pat( |
201 | replacement_text.push_str(new_name); | 200 | sema, |
202 | reference.file_range.range | 201 | FileRange { file_id, range: reference.range }, |
203 | } | 202 | new_name, |
204 | }; | 203 | ) |
205 | SourceFileEdit { | 204 | } |
206 | file_id: reference.file_range.file_id, | 205 | _ => { |
207 | edit: TextEdit::replace(range, replacement_text), | 206 | replacement_text.push_str(new_name); |
207 | reference.range | ||
208 | } | ||
209 | }; | ||
210 | edit.replace(range, replacement_text); | ||
208 | } | 211 | } |
212 | |||
213 | SourceFileEdit { file_id, edit: edit.finish() } | ||
209 | } | 214 | } |
210 | 215 | ||
211 | fn edit_text_range_for_record_field_expr_or_pat( | 216 | fn edit_text_range_for_record_field_expr_or_pat( |
@@ -276,10 +281,9 @@ fn rename_mod( | |||
276 | } | 281 | } |
277 | 282 | ||
278 | let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; | 283 | let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; |
279 | let ref_edits = refs | 284 | let ref_edits = refs.references().iter().map(|(&file_id, references)| { |
280 | .references | 285 | source_edit_from_references(sema, file_id, references, new_name) |
281 | .into_iter() | 286 | }); |
282 | .map(|reference| source_edit_from_reference(sema, reference, new_name)); | ||
283 | source_file_edits.extend(ref_edits); | 287 | source_file_edits.extend(ref_edits); |
284 | 288 | ||
285 | Ok(RangeInfo::new(range, SourceChange::from_edits(source_file_edits, file_system_edits))) | 289 | Ok(RangeInfo::new(range, SourceChange::from_edits(source_file_edits, file_system_edits))) |
@@ -331,17 +335,12 @@ fn rename_to_self( | |||
331 | 335 | ||
332 | let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; | 336 | let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; |
333 | 337 | ||
334 | let (param_ref, usages): (Vec<Reference>, Vec<Reference>) = refs | 338 | let mut edits = refs |
335 | .into_iter() | 339 | .references() |
336 | .partition(|reference| param_range.intersect(reference.file_range.range).is_some()); | 340 | .iter() |
337 | 341 | .map(|(&file_id, references)| { | |
338 | if param_ref.is_empty() { | 342 | source_edit_from_references(sema, file_id, references, "self") |
339 | bail!("Parameter to rename not found"); | 343 | }) |
340 | } | ||
341 | |||
342 | let mut edits = usages | ||
343 | .into_iter() | ||
344 | .map(|reference| source_edit_from_reference(sema, reference, "self")) | ||
345 | .collect::<Vec<_>>(); | 344 | .collect::<Vec<_>>(); |
346 | 345 | ||
347 | edits.push(SourceFileEdit { | 346 | edits.push(SourceFileEdit { |
@@ -467,7 +466,9 @@ fn rename_reference( | |||
467 | 466 | ||
468 | let edit = refs | 467 | let edit = refs |
469 | .into_iter() | 468 | .into_iter() |
470 | .map(|reference| source_edit_from_reference(sema, reference, new_name)) | 469 | .map(|(file_id, references)| { |
470 | source_edit_from_references(sema, file_id, &references, new_name) | ||
471 | }) | ||
471 | .collect::<Vec<_>>(); | 472 | .collect::<Vec<_>>(); |
472 | 473 | ||
473 | Ok(RangeInfo::new(range, SourceChange::from(edit))) | 474 | Ok(RangeInfo::new(range, SourceChange::from(edit))) |
diff --git a/crates/ide/src/syntax_highlighting/highlights.rs b/crates/ide/src/syntax_highlighting/highlights.rs index c6f0417ec..882a685a5 100644 --- a/crates/ide/src/syntax_highlighting/highlights.rs +++ b/crates/ide/src/syntax_highlighting/highlights.rs | |||
@@ -1,5 +1,5 @@ | |||
1 | //! Collects a tree of highlighted ranges and flattens it. | 1 | //! Collects a tree of highlighted ranges and flattens it. |
2 | use std::{cmp::Ordering, iter}; | 2 | use std::iter; |
3 | 3 | ||
4 | use stdx::equal_range_by; | 4 | use stdx::equal_range_by; |
5 | use syntax::TextRange; | 5 | use syntax::TextRange; |
@@ -52,7 +52,7 @@ impl Node { | |||
52 | } | 52 | } |
53 | 53 | ||
54 | let overlapping = | 54 | let overlapping = |
55 | equal_range_by(&self.nested, |n| ordering(n.hl_range.range, hl_range.range)); | 55 | equal_range_by(&self.nested, |n| TextRange::ordering(n.hl_range.range, hl_range.range)); |
56 | 56 | ||
57 | if overlapping.len() == 1 | 57 | if overlapping.len() == 1 |
58 | && self.nested[overlapping.start].hl_range.range.contains_range(hl_range.range) | 58 | && self.nested[overlapping.start].hl_range.range.contains_range(hl_range.range) |
@@ -90,13 +90,3 @@ impl Node { | |||
90 | } | 90 | } |
91 | } | 91 | } |
92 | } | 92 | } |
93 | |||
94 | pub(super) fn ordering(r1: TextRange, r2: TextRange) -> Ordering { | ||
95 | if r1.end() <= r2.start() { | ||
96 | Ordering::Less | ||
97 | } else if r2.end() <= r1.start() { | ||
98 | Ordering::Greater | ||
99 | } else { | ||
100 | Ordering::Equal | ||
101 | } | ||
102 | } | ||
diff --git a/crates/ide/src/syntax_highlighting/injector.rs b/crates/ide/src/syntax_highlighting/injector.rs index fd4025694..24ff473ec 100644 --- a/crates/ide/src/syntax_highlighting/injector.rs +++ b/crates/ide/src/syntax_highlighting/injector.rs | |||
@@ -5,8 +5,6 @@ use std::ops::{self, Sub}; | |||
5 | use stdx::equal_range_by; | 5 | use stdx::equal_range_by; |
6 | use syntax::{TextRange, TextSize}; | 6 | use syntax::{TextRange, TextSize}; |
7 | 7 | ||
8 | use super::highlights::ordering; | ||
9 | |||
10 | #[derive(Default)] | 8 | #[derive(Default)] |
11 | pub(super) struct Injector { | 9 | pub(super) struct Injector { |
12 | buf: String, | 10 | buf: String, |
@@ -33,7 +31,7 @@ impl Injector { | |||
33 | &self.buf | 31 | &self.buf |
34 | } | 32 | } |
35 | pub(super) fn map_range_up(&self, range: TextRange) -> impl Iterator<Item = TextRange> + '_ { | 33 | pub(super) fn map_range_up(&self, range: TextRange) -> impl Iterator<Item = TextRange> + '_ { |
36 | equal_range_by(&self.ranges, |&(r, _)| ordering(r, range)).filter_map(move |i| { | 34 | equal_range_by(&self.ranges, |&(r, _)| TextRange::ordering(r, range)).filter_map(move |i| { |
37 | let (target_range, delta) = self.ranges[i]; | 35 | let (target_range, delta) = self.ranges[i]; |
38 | let intersection = target_range.intersect(range).unwrap(); | 36 | let intersection = target_range.intersect(range).unwrap(); |
39 | Some(intersection + delta?) | 37 | Some(intersection + delta?) |
diff --git a/crates/ide_db/src/search.rs b/crates/ide_db/src/search.rs index 773bfbc2c..b5fa46642 100644 --- a/crates/ide_db/src/search.rs +++ b/crates/ide_db/src/search.rs | |||
@@ -18,9 +18,43 @@ use crate::{ | |||
18 | RootDatabase, | 18 | RootDatabase, |
19 | }; | 19 | }; |
20 | 20 | ||
21 | #[derive(Debug, Default, Clone)] | ||
22 | pub struct UsageSearchResult { | ||
23 | pub references: FxHashMap<FileId, Vec<FileReference>>, | ||
24 | } | ||
25 | |||
26 | impl UsageSearchResult { | ||
27 | pub fn is_empty(&self) -> bool { | ||
28 | self.references.is_empty() | ||
29 | } | ||
30 | |||
31 | pub fn len(&self) -> usize { | ||
32 | self.references.len() | ||
33 | } | ||
34 | |||
35 | pub fn iter(&self) -> impl Iterator<Item = (&FileId, &Vec<FileReference>)> + '_ { | ||
36 | self.references.iter() | ||
37 | } | ||
38 | |||
39 | pub fn file_ranges(&self) -> impl Iterator<Item = FileRange> + '_ { | ||
40 | self.references.iter().flat_map(|(&file_id, refs)| { | ||
41 | refs.iter().map(move |&FileReference { range, .. }| FileRange { file_id, range }) | ||
42 | }) | ||
43 | } | ||
44 | } | ||
45 | |||
46 | impl IntoIterator for UsageSearchResult { | ||
47 | type Item = (FileId, Vec<FileReference>); | ||
48 | type IntoIter = <FxHashMap<FileId, Vec<FileReference>> as IntoIterator>::IntoIter; | ||
49 | |||
50 | fn into_iter(self) -> Self::IntoIter { | ||
51 | self.references.into_iter() | ||
52 | } | ||
53 | } | ||
54 | |||
21 | #[derive(Debug, Clone)] | 55 | #[derive(Debug, Clone)] |
22 | pub struct Reference { | 56 | pub struct FileReference { |
23 | pub file_range: FileRange, | 57 | pub range: TextRange, |
24 | pub kind: ReferenceKind, | 58 | pub kind: ReferenceKind, |
25 | pub access: Option<ReferenceAccess>, | 59 | pub access: Option<ReferenceAccess>, |
26 | } | 60 | } |
@@ -252,23 +286,23 @@ impl<'a> FindUsages<'a> { | |||
252 | 286 | ||
253 | pub fn at_least_one(self) -> bool { | 287 | pub fn at_least_one(self) -> bool { |
254 | let mut found = false; | 288 | let mut found = false; |
255 | self.search(&mut |_reference| { | 289 | self.search(&mut |_, _| { |
256 | found = true; | 290 | found = true; |
257 | true | 291 | true |
258 | }); | 292 | }); |
259 | found | 293 | found |
260 | } | 294 | } |
261 | 295 | ||
262 | pub fn all(self) -> Vec<Reference> { | 296 | pub fn all(self) -> UsageSearchResult { |
263 | let mut res = Vec::new(); | 297 | let mut res = UsageSearchResult::default(); |
264 | self.search(&mut |reference| { | 298 | self.search(&mut |file_id, reference| { |
265 | res.push(reference); | 299 | res.references.entry(file_id).or_default().push(reference); |
266 | false | 300 | false |
267 | }); | 301 | }); |
268 | res | 302 | res |
269 | } | 303 | } |
270 | 304 | ||
271 | fn search(self, sink: &mut dyn FnMut(Reference) -> bool) { | 305 | fn search(self, sink: &mut dyn FnMut(FileId, FileReference) -> bool) { |
272 | let _p = profile::span("FindUsages:search"); | 306 | let _p = profile::span("FindUsages:search"); |
273 | let sema = self.sema; | 307 | let sema = self.sema; |
274 | 308 | ||
@@ -320,16 +354,14 @@ impl<'a> FindUsages<'a> { | |||
320 | fn found_lifetime( | 354 | fn found_lifetime( |
321 | &self, | 355 | &self, |
322 | lifetime: &ast::Lifetime, | 356 | lifetime: &ast::Lifetime, |
323 | sink: &mut dyn FnMut(Reference) -> bool, | 357 | sink: &mut dyn FnMut(FileId, FileReference) -> bool, |
324 | ) -> bool { | 358 | ) -> bool { |
325 | match NameRefClass::classify_lifetime(self.sema, lifetime) { | 359 | match NameRefClass::classify_lifetime(self.sema, lifetime) { |
326 | Some(NameRefClass::Definition(def)) if &def == self.def => { | 360 | Some(NameRefClass::Definition(def)) if &def == self.def => { |
327 | let reference = Reference { | 361 | let FileRange { file_id, range } = self.sema.original_range(lifetime.syntax()); |
328 | file_range: self.sema.original_range(lifetime.syntax()), | 362 | let reference = |
329 | kind: ReferenceKind::Lifetime, | 363 | FileReference { range, kind: ReferenceKind::Lifetime, access: None }; |
330 | access: None, | 364 | sink(file_id, reference) |
331 | }; | ||
332 | sink(reference) | ||
333 | } | 365 | } |
334 | _ => false, // not a usage | 366 | _ => false, // not a usage |
335 | } | 367 | } |
@@ -338,7 +370,7 @@ impl<'a> FindUsages<'a> { | |||
338 | fn found_name_ref( | 370 | fn found_name_ref( |
339 | &self, | 371 | &self, |
340 | name_ref: &ast::NameRef, | 372 | name_ref: &ast::NameRef, |
341 | sink: &mut dyn FnMut(Reference) -> bool, | 373 | sink: &mut dyn FnMut(FileId, FileReference) -> bool, |
342 | ) -> bool { | 374 | ) -> bool { |
343 | match NameRefClass::classify(self.sema, &name_ref) { | 375 | match NameRefClass::classify(self.sema, &name_ref) { |
344 | Some(NameRefClass::Definition(def)) if &def == self.def => { | 376 | Some(NameRefClass::Definition(def)) if &def == self.def => { |
@@ -352,46 +384,50 @@ impl<'a> FindUsages<'a> { | |||
352 | ReferenceKind::Other | 384 | ReferenceKind::Other |
353 | }; | 385 | }; |
354 | 386 | ||
355 | let reference = Reference { | 387 | let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax()); |
356 | file_range: self.sema.original_range(name_ref.syntax()), | 388 | let reference = |
357 | kind, | 389 | FileReference { range, kind, access: reference_access(&def, &name_ref) }; |
358 | access: reference_access(&def, &name_ref), | 390 | sink(file_id, reference) |
359 | }; | ||
360 | sink(reference) | ||
361 | } | 391 | } |
362 | Some(NameRefClass::FieldShorthand { local_ref: local, field_ref: field }) => { | 392 | Some(NameRefClass::FieldShorthand { local_ref: local, field_ref: field }) => { |
393 | let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax()); | ||
363 | let reference = match self.def { | 394 | let reference = match self.def { |
364 | Definition::Field(_) if &field == self.def => Reference { | 395 | Definition::Field(_) if &field == self.def => FileReference { |
365 | file_range: self.sema.original_range(name_ref.syntax()), | 396 | range, |
366 | kind: ReferenceKind::FieldShorthandForField, | 397 | kind: ReferenceKind::FieldShorthandForField, |
367 | access: reference_access(&field, &name_ref), | 398 | access: reference_access(&field, &name_ref), |
368 | }, | 399 | }, |
369 | Definition::Local(l) if &local == l => Reference { | 400 | Definition::Local(l) if &local == l => FileReference { |
370 | file_range: self.sema.original_range(name_ref.syntax()), | 401 | range, |
371 | kind: ReferenceKind::FieldShorthandForLocal, | 402 | kind: ReferenceKind::FieldShorthandForLocal, |
372 | access: reference_access(&Definition::Local(local), &name_ref), | 403 | access: reference_access(&Definition::Local(local), &name_ref), |
373 | }, | 404 | }, |
374 | _ => return false, // not a usage | 405 | _ => return false, // not a usage |
375 | }; | 406 | }; |
376 | sink(reference) | 407 | sink(file_id, reference) |
377 | } | 408 | } |
378 | _ => false, // not a usage | 409 | _ => false, // not a usage |
379 | } | 410 | } |
380 | } | 411 | } |
381 | 412 | ||
382 | fn found_name(&self, name: &ast::Name, sink: &mut dyn FnMut(Reference) -> bool) -> bool { | 413 | fn found_name( |
414 | &self, | ||
415 | name: &ast::Name, | ||
416 | sink: &mut dyn FnMut(FileId, FileReference) -> bool, | ||
417 | ) -> bool { | ||
383 | match NameClass::classify(self.sema, name) { | 418 | match NameClass::classify(self.sema, name) { |
384 | Some(NameClass::PatFieldShorthand { local_def: _, field_ref }) => { | 419 | Some(NameClass::PatFieldShorthand { local_def: _, field_ref }) => { |
385 | let reference = match self.def { | 420 | if !matches!(self.def, Definition::Field(_) if &field_ref == self.def) { |
386 | Definition::Field(_) if &field_ref == self.def => Reference { | 421 | return false; |
387 | file_range: self.sema.original_range(name.syntax()), | 422 | } |
388 | kind: ReferenceKind::FieldShorthandForField, | 423 | let FileRange { file_id, range } = self.sema.original_range(name.syntax()); |
389 | // FIXME: mutable patterns should have `Write` access | 424 | let reference = FileReference { |
390 | access: Some(ReferenceAccess::Read), | 425 | range, |
391 | }, | 426 | kind: ReferenceKind::FieldShorthandForField, |
392 | _ => return false, // not a usage | 427 | // FIXME: mutable patterns should have `Write` access |
428 | access: Some(ReferenceAccess::Read), | ||
393 | }; | 429 | }; |
394 | sink(reference) | 430 | sink(file_id, reference) |
395 | } | 431 | } |
396 | _ => false, // not a usage | 432 | _ => false, // not a usage |
397 | } | 433 | } |
diff --git a/crates/rust-analyzer/src/caps.rs b/crates/rust-analyzer/src/caps.rs index 3db0d55c5..f148521a2 100644 --- a/crates/rust-analyzer/src/caps.rs +++ b/crates/rust-analyzer/src/caps.rs | |||
@@ -75,14 +75,24 @@ pub fn server_capabilities(client_caps: &ClientCapabilities) -> ServerCapabiliti | |||
75 | will_create: None, | 75 | will_create: None, |
76 | did_rename: None, | 76 | did_rename: None, |
77 | will_rename: Some(FileOperationRegistrationOptions { | 77 | will_rename: Some(FileOperationRegistrationOptions { |
78 | filters: vec![FileOperationFilter { | 78 | filters: vec![ |
79 | scheme: Some(String::from("file")), | 79 | FileOperationFilter { |
80 | pattern: FileOperationPattern { | 80 | scheme: Some(String::from("file")), |
81 | glob: String::from("**/*.rs"), | 81 | pattern: FileOperationPattern { |
82 | matches: Some(FileOperationPatternKind::File), | 82 | glob: String::from("**/*.rs"), |
83 | options: None, | 83 | matches: Some(FileOperationPatternKind::File), |
84 | options: None, | ||
85 | }, | ||
84 | }, | 86 | }, |
85 | }], | 87 | FileOperationFilter { |
88 | scheme: Some(String::from("file")), | ||
89 | pattern: FileOperationPattern { | ||
90 | glob: String::from("**"), | ||
91 | matches: Some(FileOperationPatternKind::Folder), | ||
92 | options: None, | ||
93 | }, | ||
94 | }, | ||
95 | ], | ||
86 | }), | 96 | }), |
87 | did_delete: None, | 97 | did_delete: None, |
88 | will_delete: None, | 98 | will_delete: None, |
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index 29cc9051e..dc81f55d6 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs | |||
@@ -432,9 +432,27 @@ pub(crate) fn handle_will_rename_files( | |||
432 | // Limit to single-level moves for now. | 432 | // Limit to single-level moves for now. |
433 | match (from_path.parent(), to_path.parent()) { | 433 | match (from_path.parent(), to_path.parent()) { |
434 | (Some(p1), Some(p2)) if p1 == p2 => { | 434 | (Some(p1), Some(p2)) if p1 == p2 => { |
435 | let new_name = to_path.file_stem()?; | 435 | if from_path.is_dir() { |
436 | let new_name = new_name.to_str()?; | 436 | // add '/' to end of url -- from `file://path/to/folder` to `file://path/to/folder/` |
437 | Some((snap.url_to_file_id(&from).ok()?, new_name.to_string())) | 437 | let mut old_folder_name = from_path.file_stem()?.to_str()?.to_string(); |
438 | old_folder_name.push('/'); | ||
439 | let from_with_trailing_slash = from.join(&old_folder_name).ok()?; | ||
440 | |||
441 | let imitate_from_url = from_with_trailing_slash.join("mod.rs").ok()?; | ||
442 | let new_file_name = to_path.file_name()?.to_str()?; | ||
443 | Some(( | ||
444 | snap.url_to_file_id(&imitate_from_url).ok()?, | ||
445 | new_file_name.to_string(), | ||
446 | )) | ||
447 | } else { | ||
448 | let old_name = from_path.file_stem()?.to_str()?; | ||
449 | let new_name = to_path.file_stem()?.to_str()?; | ||
450 | match (old_name, new_name) { | ||
451 | ("mod", _) => None, | ||
452 | (_, "mod") => None, | ||
453 | _ => Some((snap.url_to_file_id(&from).ok()?, new_name.to_string())), | ||
454 | } | ||
455 | } | ||
438 | } | 456 | } |
439 | _ => None, | 457 | _ => None, |
440 | } | 458 | } |
@@ -773,7 +791,8 @@ pub(crate) fn handle_prepare_rename( | |||
773 | let _p = profile::span("handle_prepare_rename"); | 791 | let _p = profile::span("handle_prepare_rename"); |
774 | let position = from_proto::file_position(&snap, params)?; | 792 | let position = from_proto::file_position(&snap, params)?; |
775 | 793 | ||
776 | let change = snap.analysis.prepare_rename(position)??; | 794 | let change = snap.analysis.prepare_rename(position)?.map_err(to_proto::rename_error)?; |
795 | |||
777 | let line_index = snap.analysis.file_line_index(position.file_id)?; | 796 | let line_index = snap.analysis.file_line_index(position.file_id)?; |
778 | let range = to_proto::range(&line_index, change.range); | 797 | let range = to_proto::range(&line_index, change.range); |
779 | Ok(Some(PrepareRenameResponse::Range(range))) | 798 | Ok(Some(PrepareRenameResponse::Range(range))) |
@@ -786,15 +805,8 @@ pub(crate) fn handle_rename( | |||
786 | let _p = profile::span("handle_rename"); | 805 | let _p = profile::span("handle_rename"); |
787 | let position = from_proto::file_position(&snap, params.text_document_position)?; | 806 | let position = from_proto::file_position(&snap, params.text_document_position)?; |
788 | 807 | ||
789 | if params.new_name.is_empty() { | 808 | let change = |
790 | return Err(LspError::new( | 809 | snap.analysis.rename(position, &*params.new_name)?.map_err(to_proto::rename_error)?; |
791 | ErrorCode::InvalidParams as i32, | ||
792 | "New Name cannot be empty".into(), | ||
793 | ) | ||
794 | .into()); | ||
795 | } | ||
796 | |||
797 | let change = snap.analysis.rename(position, &*params.new_name)??; | ||
798 | let workspace_edit = to_proto::workspace_edit(&snap, change.info)?; | 810 | let workspace_edit = to_proto::workspace_edit(&snap, change.info)?; |
799 | Ok(Some(workspace_edit)) | 811 | Ok(Some(workspace_edit)) |
800 | } | 812 | } |
@@ -812,14 +824,15 @@ pub(crate) fn handle_references( | |||
812 | }; | 824 | }; |
813 | 825 | ||
814 | let locations = if params.context.include_declaration { | 826 | let locations = if params.context.include_declaration { |
815 | refs.into_iter() | 827 | refs.references_with_declaration() |
816 | .filter_map(|reference| to_proto::location(&snap, reference.file_range).ok()) | 828 | .file_ranges() |
829 | .filter_map(|frange| to_proto::location(&snap, frange).ok()) | ||
817 | .collect() | 830 | .collect() |
818 | } else { | 831 | } else { |
819 | // Only iterate over the references if include_declaration was false | 832 | // Only iterate over the references if include_declaration was false |
820 | refs.references() | 833 | refs.references() |
821 | .iter() | 834 | .file_ranges() |
822 | .filter_map(|reference| to_proto::location(&snap, reference.file_range).ok()) | 835 | .filter_map(|frange| to_proto::location(&snap, frange).ok()) |
823 | .collect() | 836 | .collect() |
824 | }; | 837 | }; |
825 | 838 | ||
@@ -1175,8 +1188,8 @@ pub(crate) fn handle_code_lens_resolve( | |||
1175 | .unwrap_or(None) | 1188 | .unwrap_or(None) |
1176 | .map(|r| { | 1189 | .map(|r| { |
1177 | r.references() | 1190 | r.references() |
1178 | .iter() | 1191 | .file_ranges() |
1179 | .filter_map(|it| to_proto::location(&snap, it.file_range).ok()) | 1192 | .filter_map(|frange| to_proto::location(&snap, frange).ok()) |
1180 | .collect_vec() | 1193 | .collect_vec() |
1181 | }) | 1194 | }) |
1182 | .unwrap_or_default(); | 1195 | .unwrap_or_default(); |
@@ -1220,13 +1233,19 @@ pub(crate) fn handle_document_highlight( | |||
1220 | }; | 1233 | }; |
1221 | 1234 | ||
1222 | let res = refs | 1235 | let res = refs |
1223 | .into_iter() | 1236 | .references_with_declaration() |
1224 | .filter(|reference| reference.file_range.file_id == position.file_id) | 1237 | .references |
1225 | .map(|reference| DocumentHighlight { | 1238 | .get(&position.file_id) |
1226 | range: to_proto::range(&line_index, reference.file_range.range), | 1239 | .map(|file_refs| { |
1227 | kind: reference.access.map(to_proto::document_highlight_kind), | 1240 | file_refs |
1241 | .into_iter() | ||
1242 | .map(|r| DocumentHighlight { | ||
1243 | range: to_proto::range(&line_index, r.range), | ||
1244 | kind: r.access.map(to_proto::document_highlight_kind), | ||
1245 | }) | ||
1246 | .collect() | ||
1228 | }) | 1247 | }) |
1229 | .collect(); | 1248 | .unwrap_or_default(); |
1230 | Ok(Some(res)) | 1249 | Ok(Some(res)) |
1231 | } | 1250 | } |
1232 | 1251 | ||
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index bdddca9da..a7ff8975a 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs | |||
@@ -8,7 +8,8 @@ use ide::{ | |||
8 | Assist, AssistKind, CallInfo, CompletionItem, CompletionItemKind, Documentation, FileId, | 8 | Assist, AssistKind, CallInfo, CompletionItem, CompletionItemKind, Documentation, FileId, |
9 | FileRange, FileSystemEdit, Fold, FoldKind, Highlight, HlMod, HlPunct, HlRange, HlTag, Indel, | 9 | FileRange, FileSystemEdit, Fold, FoldKind, Highlight, HlMod, HlPunct, HlRange, HlTag, Indel, |
10 | InlayHint, InlayKind, InsertTextFormat, LineIndex, Markup, NavigationTarget, ReferenceAccess, | 10 | InlayHint, InlayKind, InsertTextFormat, LineIndex, Markup, NavigationTarget, ReferenceAccess, |
11 | Runnable, Severity, SourceChange, SourceFileEdit, SymbolKind, TextEdit, TextRange, TextSize, | 11 | RenameError, Runnable, Severity, SourceChange, SourceFileEdit, SymbolKind, TextEdit, TextRange, |
12 | TextSize, | ||
12 | }; | 13 | }; |
13 | use itertools::Itertools; | 14 | use itertools::Itertools; |
14 | 15 | ||
@@ -855,6 +856,10 @@ pub(crate) fn markup_content(markup: Markup) -> lsp_types::MarkupContent { | |||
855 | lsp_types::MarkupContent { kind: lsp_types::MarkupKind::Markdown, value } | 856 | lsp_types::MarkupContent { kind: lsp_types::MarkupKind::Markdown, value } |
856 | } | 857 | } |
857 | 858 | ||
859 | pub(crate) fn rename_error(err: RenameError) -> crate::LspError { | ||
860 | crate::LspError { code: lsp_server::ErrorCode::InvalidParams as i32, message: err.to_string() } | ||
861 | } | ||
862 | |||
858 | #[cfg(test)] | 863 | #[cfg(test)] |
859 | mod tests { | 864 | mod tests { |
860 | use ide::Analysis; | 865 | use ide::Analysis; |
diff --git a/crates/rust-analyzer/tests/rust-analyzer/main.rs b/crates/rust-analyzer/tests/rust-analyzer/main.rs index f5c5c63e5..80bde29b9 100644 --- a/crates/rust-analyzer/tests/rust-analyzer/main.rs +++ b/crates/rust-analyzer/tests/rust-analyzer/main.rs | |||
@@ -16,11 +16,14 @@ use std::{collections::HashMap, path::PathBuf, time::Instant}; | |||
16 | use expect_test::expect; | 16 | use expect_test::expect; |
17 | use lsp_types::{ | 17 | use lsp_types::{ |
18 | notification::DidOpenTextDocument, | 18 | notification::DidOpenTextDocument, |
19 | request::{CodeActionRequest, Completion, Formatting, GotoTypeDefinition, HoverRequest}, | 19 | request::{ |
20 | CodeActionRequest, Completion, Formatting, GotoTypeDefinition, HoverRequest, | ||
21 | WillRenameFiles, | ||
22 | }, | ||
20 | CodeActionContext, CodeActionParams, CompletionParams, DidOpenTextDocumentParams, | 23 | CodeActionContext, CodeActionParams, CompletionParams, DidOpenTextDocumentParams, |
21 | DocumentFormattingParams, FormattingOptions, GotoDefinitionParams, HoverParams, | 24 | DocumentFormattingParams, FileRename, FormattingOptions, GotoDefinitionParams, HoverParams, |
22 | PartialResultParams, Position, Range, TextDocumentItem, TextDocumentPositionParams, | 25 | PartialResultParams, Position, Range, RenameFilesParams, TextDocumentItem, |
23 | WorkDoneProgressParams, | 26 | TextDocumentPositionParams, WorkDoneProgressParams, |
24 | }; | 27 | }; |
25 | use rust_analyzer::lsp_ext::{OnEnter, Runnables, RunnablesParams}; | 28 | use rust_analyzer::lsp_ext::{OnEnter, Runnables, RunnablesParams}; |
26 | use serde_json::json; | 29 | use serde_json::json; |
@@ -745,3 +748,136 @@ pub fn foo(_input: TokenStream) -> TokenStream { | |||
745 | ```"#]] | 748 | ```"#]] |
746 | .assert_eq(&value); | 749 | .assert_eq(&value); |
747 | } | 750 | } |
751 | |||
752 | #[test] | ||
753 | fn test_will_rename_files_same_level() { | ||
754 | if skip_slow_tests() { | ||
755 | return; | ||
756 | } | ||
757 | |||
758 | let tmp_dir = TestDir::new(); | ||
759 | let tmp_dir_path = tmp_dir.path().to_owned(); | ||
760 | let tmp_dir_str = tmp_dir_path.to_str().unwrap(); | ||
761 | let base_path = PathBuf::from(format!("file://{}", tmp_dir_str)); | ||
762 | |||
763 | let code = r#" | ||
764 | //- /Cargo.toml | ||
765 | [package] | ||
766 | name = "foo" | ||
767 | version = "0.0.0" | ||
768 | |||
769 | //- /src/lib.rs | ||
770 | mod old_file; | ||
771 | mod from_mod; | ||
772 | mod to_mod; | ||
773 | mod old_folder; | ||
774 | fn main() {} | ||
775 | |||
776 | //- /src/old_file.rs | ||
777 | |||
778 | //- /src/old_folder/mod.rs | ||
779 | |||
780 | //- /src/from_mod/mod.rs | ||
781 | |||
782 | //- /src/to_mod/foo.rs | ||
783 | |||
784 | "#; | ||
785 | let server = | ||
786 | Project::with_fixture(&code).tmp_dir(tmp_dir).server().wait_until_workspace_is_loaded(); | ||
787 | |||
788 | //rename same level file | ||
789 | server.request::<WillRenameFiles>( | ||
790 | RenameFilesParams { | ||
791 | files: vec![FileRename { | ||
792 | old_uri: base_path.join("src/old_file.rs").to_str().unwrap().to_string(), | ||
793 | new_uri: base_path.join("src/new_file.rs").to_str().unwrap().to_string(), | ||
794 | }], | ||
795 | }, | ||
796 | json!({ | ||
797 | "documentChanges": [ | ||
798 | { | ||
799 | "textDocument": { | ||
800 | "uri": format!("file://{}", tmp_dir_path.join("src").join("lib.rs").to_str().unwrap().to_string().replace("C:\\", "/c:/").replace("\\", "/")), | ||
801 | "version": null | ||
802 | }, | ||
803 | "edits": [ | ||
804 | { | ||
805 | "range": { | ||
806 | "start": { | ||
807 | "line": 0, | ||
808 | "character": 4 | ||
809 | }, | ||
810 | "end": { | ||
811 | "line": 0, | ||
812 | "character": 12 | ||
813 | } | ||
814 | }, | ||
815 | "newText": "new_file" | ||
816 | } | ||
817 | ] | ||
818 | } | ||
819 | ] | ||
820 | }), | ||
821 | ); | ||
822 | |||
823 | //rename file from mod.rs to foo.rs | ||
824 | server.request::<WillRenameFiles>( | ||
825 | RenameFilesParams { | ||
826 | files: vec![FileRename { | ||
827 | old_uri: base_path.join("src/from_mod/mod.rs").to_str().unwrap().to_string(), | ||
828 | new_uri: base_path.join("src/from_mod/foo.rs").to_str().unwrap().to_string(), | ||
829 | }], | ||
830 | }, | ||
831 | json!({ | ||
832 | "documentChanges": [] | ||
833 | }), | ||
834 | ); | ||
835 | |||
836 | //rename file from foo.rs to mod.rs | ||
837 | server.request::<WillRenameFiles>( | ||
838 | RenameFilesParams { | ||
839 | files: vec![FileRename { | ||
840 | old_uri: base_path.join("src/to_mod/foo.rs").to_str().unwrap().to_string(), | ||
841 | new_uri: base_path.join("src/to_mod/mod.rs").to_str().unwrap().to_string(), | ||
842 | }], | ||
843 | }, | ||
844 | json!({ | ||
845 | "documentChanges": [] | ||
846 | }), | ||
847 | ); | ||
848 | |||
849 | //rename same level file | ||
850 | server.request::<WillRenameFiles>( | ||
851 | RenameFilesParams { | ||
852 | files: vec![FileRename { | ||
853 | old_uri: base_path.join("src/old_folder").to_str().unwrap().to_string(), | ||
854 | new_uri: base_path.join("src/new_folder").to_str().unwrap().to_string(), | ||
855 | }], | ||
856 | }, | ||
857 | json!({ | ||
858 | "documentChanges": [ | ||
859 | { | ||
860 | "textDocument": { | ||
861 | "uri": format!("file://{}", tmp_dir_path.join("src").join("lib.rs").to_str().unwrap().to_string().replace("C:\\", "/c:/").replace("\\", "/")), | ||
862 | "version": null | ||
863 | }, | ||
864 | "edits": [ | ||
865 | { | ||
866 | "range": { | ||
867 | "start": { | ||
868 | "line": 3, | ||
869 | "character": 4 | ||
870 | }, | ||
871 | "end": { | ||
872 | "line": 3, | ||
873 | "character": 14 | ||
874 | } | ||
875 | }, | ||
876 | "newText": "new_folder" | ||
877 | } | ||
878 | ] | ||
879 | } | ||
880 | ] | ||
881 | }), | ||
882 | ); | ||
883 | } | ||
diff --git a/crates/ssr/src/search.rs b/crates/ssr/src/search.rs index 44b5db029..836eb94b2 100644 --- a/crates/ssr/src/search.rs +++ b/crates/ssr/src/search.rs | |||
@@ -5,10 +5,10 @@ use crate::{ | |||
5 | resolving::{ResolvedPath, ResolvedPattern, ResolvedRule}, | 5 | resolving::{ResolvedPath, ResolvedPattern, ResolvedRule}, |
6 | Match, MatchFinder, | 6 | Match, MatchFinder, |
7 | }; | 7 | }; |
8 | use ide_db::base_db::{FileId, FileRange}; | ||
9 | use ide_db::{ | 8 | use ide_db::{ |
9 | base_db::{FileId, FileRange}, | ||
10 | defs::Definition, | 10 | defs::Definition, |
11 | search::{Reference, SearchScope}, | 11 | search::{SearchScope, UsageSearchResult}, |
12 | }; | 12 | }; |
13 | use rustc_hash::FxHashSet; | 13 | use rustc_hash::FxHashSet; |
14 | use syntax::{ast, AstNode, SyntaxKind, SyntaxNode}; | 14 | use syntax::{ast, AstNode, SyntaxKind, SyntaxNode}; |
@@ -20,7 +20,7 @@ use test_utils::mark; | |||
20 | /// them more than once. | 20 | /// them more than once. |
21 | #[derive(Default)] | 21 | #[derive(Default)] |
22 | pub(crate) struct UsageCache { | 22 | pub(crate) struct UsageCache { |
23 | usages: Vec<(Definition, Vec<Reference>)>, | 23 | usages: Vec<(Definition, UsageSearchResult)>, |
24 | } | 24 | } |
25 | 25 | ||
26 | impl<'db> MatchFinder<'db> { | 26 | impl<'db> MatchFinder<'db> { |
@@ -58,8 +58,8 @@ impl<'db> MatchFinder<'db> { | |||
58 | ) { | 58 | ) { |
59 | if let Some(resolved_path) = pick_path_for_usages(pattern) { | 59 | if let Some(resolved_path) = pick_path_for_usages(pattern) { |
60 | let definition: Definition = resolved_path.resolution.clone().into(); | 60 | let definition: Definition = resolved_path.resolution.clone().into(); |
61 | for reference in self.find_usages(usage_cache, definition) { | 61 | for file_range in self.find_usages(usage_cache, definition).file_ranges() { |
62 | if let Some(node_to_match) = self.find_node_to_match(resolved_path, reference) { | 62 | if let Some(node_to_match) = self.find_node_to_match(resolved_path, file_range) { |
63 | if !is_search_permitted_ancestors(&node_to_match) { | 63 | if !is_search_permitted_ancestors(&node_to_match) { |
64 | mark::hit!(use_declaration_with_braces); | 64 | mark::hit!(use_declaration_with_braces); |
65 | continue; | 65 | continue; |
@@ -73,11 +73,11 @@ impl<'db> MatchFinder<'db> { | |||
73 | fn find_node_to_match( | 73 | fn find_node_to_match( |
74 | &self, | 74 | &self, |
75 | resolved_path: &ResolvedPath, | 75 | resolved_path: &ResolvedPath, |
76 | reference: &Reference, | 76 | file_range: FileRange, |
77 | ) -> Option<SyntaxNode> { | 77 | ) -> Option<SyntaxNode> { |
78 | let file = self.sema.parse(reference.file_range.file_id); | 78 | let file = self.sema.parse(file_range.file_id); |
79 | let depth = resolved_path.depth as usize; | 79 | let depth = resolved_path.depth as usize; |
80 | let offset = reference.file_range.range.start(); | 80 | let offset = file_range.range.start(); |
81 | if let Some(path) = | 81 | if let Some(path) = |
82 | self.sema.find_node_at_offset_with_descend::<ast::Path>(file.syntax(), offset) | 82 | self.sema.find_node_at_offset_with_descend::<ast::Path>(file.syntax(), offset) |
83 | { | 83 | { |
@@ -108,7 +108,7 @@ impl<'db> MatchFinder<'db> { | |||
108 | &self, | 108 | &self, |
109 | usage_cache: &'a mut UsageCache, | 109 | usage_cache: &'a mut UsageCache, |
110 | definition: Definition, | 110 | definition: Definition, |
111 | ) -> &'a [Reference] { | 111 | ) -> &'a UsageSearchResult { |
112 | // Logically if a lookup succeeds we should just return it. Unfortunately returning it would | 112 | // Logically if a lookup succeeds we should just return it. Unfortunately returning it would |
113 | // extend the lifetime of the borrow, then we wouldn't be able to do the insertion on a | 113 | // extend the lifetime of the borrow, then we wouldn't be able to do the insertion on a |
114 | // cache miss. This is a limitation of NLL and is fixed with Polonius. For now we do two | 114 | // cache miss. This is a limitation of NLL and is fixed with Polonius. For now we do two |
@@ -250,7 +250,7 @@ fn is_search_permitted(node: &SyntaxNode) -> bool { | |||
250 | } | 250 | } |
251 | 251 | ||
252 | impl UsageCache { | 252 | impl UsageCache { |
253 | fn find(&mut self, definition: &Definition) -> Option<&[Reference]> { | 253 | fn find(&mut self, definition: &Definition) -> Option<&UsageSearchResult> { |
254 | // We expect a very small number of cache entries (generally 1), so a linear scan should be | 254 | // We expect a very small number of cache entries (generally 1), so a linear scan should be |
255 | // fast enough and avoids the need to implement Hash for Definition. | 255 | // fast enough and avoids the need to implement Hash for Definition. |
256 | for (d, refs) in &self.usages { | 256 | for (d, refs) in &self.usages { |