@@ -9,6 +9,7 @@ use ide_db::{
9
9
base_db:: { FileId , FileRange } ,
10
10
defs:: { Definition , NameClass , NameRefClass } ,
11
11
rename:: { bail, format_err, source_edit_from_references, IdentifierKind } ,
12
+ source_change:: SourceChangeBuilder ,
12
13
RootDatabase ,
13
14
} ;
14
15
use itertools:: Itertools ;
@@ -90,24 +91,60 @@ pub(crate) fn rename(
90
91
let syntax = source_file. syntax ( ) ;
91
92
92
93
let defs = find_definitions ( & sema, syntax, position) ?;
94
+ let alias_fallback = alias_fallback ( syntax, position, new_name) ;
95
+
96
+ let ops: RenameResult < Vec < SourceChange > > = match alias_fallback {
97
+ Some ( _) => defs
98
+ // FIXME: This can use the `ide_db::rename_reference` (or def.rename) method once we can
99
+ // properly find "direct" usages/references.
100
+ . map ( |( .., def) | {
101
+ match IdentifierKind :: classify ( new_name) ? {
102
+ IdentifierKind :: Ident => ( ) ,
103
+ IdentifierKind :: Lifetime => {
104
+ bail ! ( "Cannot alias reference to a lifetime identifier" )
105
+ }
106
+ IdentifierKind :: Underscore => bail ! ( "Cannot alias reference to `_`" ) ,
107
+ } ;
93
108
94
- let ops: RenameResult < Vec < SourceChange > > = defs
95
- . map ( |( .., def) | {
96
- if let Definition :: Local ( local) = def {
97
- if let Some ( self_param) = local. as_self_param ( sema. db ) {
98
- cov_mark:: hit!( rename_self_to_param) ;
99
- return rename_self_to_param ( & sema, local, self_param, new_name) ;
100
- }
101
- if new_name == "self" {
102
- cov_mark:: hit!( rename_to_self) ;
103
- return rename_to_self ( & sema, local) ;
109
+ let mut usages = def. usages ( & sema) . all ( ) ;
110
+
111
+ // FIXME: hack - removes the usage that triggered this rename operation.
112
+ match usages. references . get_mut ( & position. file_id ) . and_then ( |refs| {
113
+ refs. iter ( )
114
+ . position ( |ref_| ref_. range . contains_inclusive ( position. offset ) )
115
+ . map ( |idx| refs. remove ( idx) )
116
+ } ) {
117
+ Some ( _) => ( ) ,
118
+ None => never ! ( ) ,
119
+ } ;
120
+
121
+ let mut source_change = SourceChange :: default ( ) ;
122
+ source_change. extend ( usages. iter ( ) . map ( |( & file_id, refs) | {
123
+ ( file_id, source_edit_from_references ( refs, def, new_name) )
124
+ } ) ) ;
125
+
126
+ Ok ( source_change)
127
+ } )
128
+ . collect ( ) ,
129
+ None => defs
130
+ . map ( |( .., def) | {
131
+ if let Definition :: Local ( local) = def {
132
+ if let Some ( self_param) = local. as_self_param ( sema. db ) {
133
+ cov_mark:: hit!( rename_self_to_param) ;
134
+ return rename_self_to_param ( & sema, local, self_param, new_name) ;
135
+ }
136
+ if new_name == "self" {
137
+ cov_mark:: hit!( rename_to_self) ;
138
+ return rename_to_self ( & sema, local) ;
139
+ }
104
140
}
105
- }
106
- def . rename ( & sema , new_name )
107
- } )
108
- . collect ( ) ;
141
+ def . rename ( & sema , new_name )
142
+ } )
143
+ . collect ( ) ,
144
+ } ;
109
145
110
146
ops?. into_iter ( )
147
+ . chain ( alias_fallback)
111
148
. reduce ( |acc, elem| acc. merge ( elem) )
112
149
. ok_or_else ( || format_err ! ( "No references found at position" ) )
113
150
}
@@ -130,6 +167,38 @@ pub(crate) fn will_rename_file(
130
167
Some ( change)
131
168
}
132
169
170
+ // FIXME: Should support `extern crate`.
171
+ fn alias_fallback (
172
+ syntax : & SyntaxNode ,
173
+ FilePosition { file_id, offset } : FilePosition ,
174
+ new_name : & str ,
175
+ ) -> Option < SourceChange > {
176
+ let use_tree = syntax
177
+ . token_at_offset ( offset)
178
+ . flat_map ( |syntax| syntax. parent_ancestors ( ) )
179
+ . find_map ( ast:: UseTree :: cast) ?;
180
+
181
+ let last_path_segment = use_tree. path ( ) ?. segments ( ) . last ( ) ?. name_ref ( ) ?;
182
+ if !last_path_segment. syntax ( ) . text_range ( ) . contains_inclusive ( offset) {
183
+ return None ;
184
+ } ;
185
+
186
+ let mut builder = SourceChangeBuilder :: new ( file_id) ;
187
+
188
+ match use_tree. rename ( ) {
189
+ Some ( rename) => {
190
+ let offset = rename. syntax ( ) . text_range ( ) ;
191
+ builder. replace ( offset, format ! ( "as {new_name}" ) ) ;
192
+ }
193
+ None => {
194
+ let offset = use_tree. syntax ( ) . text_range ( ) . end ( ) ;
195
+ builder. insert ( offset, format ! ( " as {new_name}" ) ) ;
196
+ }
197
+ }
198
+
199
+ Some ( builder. finish ( ) )
200
+ }
201
+
133
202
fn find_definitions (
134
203
sema : & Semantics < ' _ , RootDatabase > ,
135
204
syntax : & SyntaxNode ,
@@ -2626,7 +2695,8 @@ use qux as frob;
2626
2695
//- /lib.rs crate:lib new_source_root:library
2627
2696
pub struct S;
2628
2697
//- /main.rs crate:main deps:lib new_source_root:local
2629
- use lib::S$0;
2698
+ use lib::S;
2699
+ fn main() { let _: S$0; }
2630
2700
"# ,
2631
2701
"error: Cannot rename a non-local definition" ,
2632
2702
) ;
@@ -2686,4 +2756,27 @@ fn test() {
2686
2756
"# ,
2687
2757
) ;
2688
2758
}
2759
+
2760
+ #[ test]
2761
+ fn rename_path_inside_use_tree ( ) {
2762
+ check (
2763
+ "Baz" ,
2764
+ r#"
2765
+ mod foo { pub struct Foo; }
2766
+ mod bar { use super::Foo; }
2767
+
2768
+ use foo::Foo$0;
2769
+
2770
+ fn main() { let _: Foo; }
2771
+ "# ,
2772
+ r#"
2773
+ mod foo { pub struct Foo; }
2774
+ mod bar { use super::Baz; }
2775
+
2776
+ use foo::Foo as Baz;
2777
+
2778
+ fn main() { let _: Baz; }
2779
+ "# ,
2780
+ )
2781
+ }
2689
2782
}
0 commit comments