diff options
Diffstat (limited to 'crates/ra_ide_api/src/rename.rs')
-rw-r--r-- | crates/ra_ide_api/src/rename.rs | 136 |
1 files changed, 136 insertions, 0 deletions
diff --git a/crates/ra_ide_api/src/rename.rs b/crates/ra_ide_api/src/rename.rs new file mode 100644 index 000000000..9f8a00ae7 --- /dev/null +++ b/crates/ra_ide_api/src/rename.rs | |||
@@ -0,0 +1,136 @@ | |||
1 | use relative_path::RelativePathBuf; | ||
2 | |||
3 | use hir::{ | ||
4 | self, ModuleSource, source_binder::module_from_declaration, | ||
5 | }; | ||
6 | use ra_syntax::{ | ||
7 | algo::find_node_at_offset, | ||
8 | ast, | ||
9 | AstNode, | ||
10 | SyntaxNode | ||
11 | }; | ||
12 | |||
13 | use crate::{ | ||
14 | db::RootDatabase, | ||
15 | FilePosition, | ||
16 | FileSystemEdit, | ||
17 | SourceChange, | ||
18 | SourceFileEdit, | ||
19 | }; | ||
20 | use ra_db::{FilesDatabase, SyntaxDatabase}; | ||
21 | use relative_path::RelativePath; | ||
22 | |||
23 | pub(crate) fn rename( | ||
24 | db: &RootDatabase, | ||
25 | position: FilePosition, | ||
26 | new_name: &str, | ||
27 | ) -> Option<SourceChange> { | ||
28 | let source_file = db.source_file(position.file_id); | ||
29 | let syntax = source_file.syntax(); | ||
30 | |||
31 | if let Some((ast_name, ast_module)) = find_name_and_module_at_offset(syntax, position) { | ||
32 | rename_mod(db, ast_name, ast_module, position, new_name) | ||
33 | } else { | ||
34 | rename_reference(db, position, new_name) | ||
35 | } | ||
36 | } | ||
37 | |||
38 | fn find_name_and_module_at_offset( | ||
39 | syntax: &SyntaxNode, | ||
40 | position: FilePosition, | ||
41 | ) -> Option<(&ast::Name, &ast::Module)> { | ||
42 | let ast_name = find_node_at_offset::<ast::Name>(syntax, position.offset); | ||
43 | let ast_name_parent = ast_name | ||
44 | .and_then(|n| n.syntax().parent()) | ||
45 | .and_then(|p| ast::Module::cast(p)); | ||
46 | |||
47 | if let (Some(ast_module), Some(name)) = (ast_name_parent, ast_name) { | ||
48 | return Some((name, ast_module)); | ||
49 | } | ||
50 | None | ||
51 | } | ||
52 | |||
53 | fn rename_mod( | ||
54 | db: &RootDatabase, | ||
55 | ast_name: &ast::Name, | ||
56 | ast_module: &ast::Module, | ||
57 | position: FilePosition, | ||
58 | new_name: &str, | ||
59 | ) -> Option<SourceChange> { | ||
60 | let mut source_file_edits = Vec::new(); | ||
61 | let mut file_system_edits = Vec::new(); | ||
62 | |||
63 | if let Some(module) = module_from_declaration(db, position.file_id, &ast_module) { | ||
64 | let (file_id, module_source) = module.definition_source(db); | ||
65 | match module_source { | ||
66 | ModuleSource::SourceFile(..) => { | ||
67 | let mod_path: RelativePathBuf = db.file_relative_path(file_id); | ||
68 | // mod is defined in path/to/dir/mod.rs | ||
69 | let dst_path = if mod_path.file_stem() == Some("mod") { | ||
70 | mod_path | ||
71 | .parent() | ||
72 | .and_then(|p| p.parent()) | ||
73 | .or_else(|| Some(RelativePath::new(""))) | ||
74 | .map(|p| p.join(new_name).join("mod.rs")) | ||
75 | } else { | ||
76 | Some(mod_path.with_file_name(new_name).with_extension("rs")) | ||
77 | }; | ||
78 | if let Some(path) = dst_path { | ||
79 | let move_file = FileSystemEdit::MoveFile { | ||
80 | src: file_id, | ||
81 | dst_source_root: db.file_source_root(position.file_id), | ||
82 | dst_path: path, | ||
83 | }; | ||
84 | file_system_edits.push(move_file); | ||
85 | } | ||
86 | } | ||
87 | ModuleSource::Module(..) => {} | ||
88 | } | ||
89 | } | ||
90 | |||
91 | let edit = SourceFileEdit { | ||
92 | file_id: position.file_id, | ||
93 | edit: { | ||
94 | let mut builder = ra_text_edit::TextEditBuilder::default(); | ||
95 | builder.replace(ast_name.syntax().range(), new_name.into()); | ||
96 | builder.finish() | ||
97 | }, | ||
98 | }; | ||
99 | source_file_edits.push(edit); | ||
100 | |||
101 | return Some(SourceChange { | ||
102 | label: "rename".to_string(), | ||
103 | source_file_edits, | ||
104 | file_system_edits, | ||
105 | cursor_position: None, | ||
106 | }); | ||
107 | } | ||
108 | |||
109 | fn rename_reference( | ||
110 | db: &RootDatabase, | ||
111 | position: FilePosition, | ||
112 | new_name: &str, | ||
113 | ) -> Option<SourceChange> { | ||
114 | let edit = db | ||
115 | .find_all_refs(position) | ||
116 | .iter() | ||
117 | .map(|(file_id, text_range)| SourceFileEdit { | ||
118 | file_id: *file_id, | ||
119 | edit: { | ||
120 | let mut builder = ra_text_edit::TextEditBuilder::default(); | ||
121 | builder.replace(*text_range, new_name.into()); | ||
122 | builder.finish() | ||
123 | }, | ||
124 | }) | ||
125 | .collect::<Vec<_>>(); | ||
126 | if edit.is_empty() { | ||
127 | return None; | ||
128 | } | ||
129 | |||
130 | return Some(SourceChange { | ||
131 | label: "rename".to_string(), | ||
132 | source_file_edits: edit, | ||
133 | file_system_edits: Vec::new(), | ||
134 | cursor_position: None, | ||
135 | }); | ||
136 | } | ||