//! FIXME: write short doc here
-use std::{
- convert::TryInto,
- error::Error,
- fmt::{self, Display},
-};
+use std::fmt::{self, Display};
use hir::{Module, ModuleDef, ModuleSource, Semantics};
-use ide_db::base_db::{AnchoredPathBuf, FileId, FileRange, SourceDatabaseExt};
use ide_db::{
+ base_db::{AnchoredPathBuf, FileId, FileRange},
defs::{Definition, NameClass, NameRefClass},
+ search::FileReference,
RootDatabase,
};
use syntax::{
algo::find_node_at_offset,
ast::{self, NameOwner},
- lex_single_syntax_kind, match_ast, AstNode, SyntaxKind, SyntaxNode, SyntaxToken, T,
+ lex_single_syntax_kind, match_ast, AstNode, SyntaxKind, SyntaxNode, T,
};
use test_utils::mark;
use text_edit::TextEdit;
use crate::{
- FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind, ReferenceSearchResult,
- SourceChange, SourceFileEdit, TextRange, TextSize,
+ FilePosition, FileSystemEdit, RangeInfo, ReferenceKind, ReferenceSearchResult, SourceChange,
+ TextRange,
};
type RenameResult<T> = Result<T, RenameError>;
}
}
-impl Error for RenameError {}
-
macro_rules! format_err {
($fmt:expr) => {RenameError(format!($fmt))};
($fmt:expr, $($arg:tt)+) => {RenameError(format!($fmt, $($arg)+))}
let syntax = source_file.syntax();
if let Some(module) = find_module_at_offset(&sema, position, syntax) {
rename_mod(&sema, position, module, "dummy")
- } else if let Some(self_token) =
- syntax.token_at_offset(position.offset).find(|t| t.kind() == T![self])
- {
- rename_self_to_param(&sema, position, self_token, "dummy")
} else {
let RangeInfo { range, .. } = find_all_refs(&sema, position)?;
- Ok(RangeInfo::new(range, SourceChange::from(vec![])))
+ Ok(RangeInfo::new(range, SourceChange::default()))
}
.map(|info| RangeInfo::new(info.range, ()))
}
if let Some(module) = find_module_at_offset(&sema, position, syntax) {
rename_mod(&sema, position, module, new_name)
- } else if let Some(self_token) =
- syntax.token_at_offset(position.offset).find(|t| t.kind() == T![self])
- {
- rename_self_to_param(&sema, position, self_token, new_name)
} else {
rename_reference(&sema, position, new_name)
}
Some(change)
}
-#[derive(Debug, PartialEq)]
+#[derive(Copy, Clone, Debug, PartialEq)]
enum IdentifierKind {
Ident,
Lifetime,
.ok_or_else(|| format_err!("No references found at position"))
}
-fn source_edit_from_reference(
+fn source_edit_from_references(
sema: &Semantics<RootDatabase>,
- reference: Reference,
+ file_id: FileId,
+ references: &[FileReference],
new_name: &str,
-) -> SourceFileEdit {
- let mut replacement_text = String::new();
- let range = match reference.kind {
- ReferenceKind::FieldShorthandForField => {
- mark::hit!(test_rename_struct_field_for_shorthand);
- replacement_text.push_str(new_name);
- replacement_text.push_str(": ");
- TextRange::new(reference.file_range.range.start(), reference.file_range.range.start())
- }
- ReferenceKind::FieldShorthandForLocal => {
- mark::hit!(test_rename_local_for_field_shorthand);
- replacement_text.push_str(": ");
- replacement_text.push_str(new_name);
- TextRange::new(reference.file_range.range.end(), reference.file_range.range.end())
- }
- ReferenceKind::RecordFieldExprOrPat => {
- mark::hit!(test_rename_field_expr_pat);
- replacement_text.push_str(new_name);
- edit_text_range_for_record_field_expr_or_pat(sema, reference.file_range, new_name)
- }
- _ => {
- replacement_text.push_str(new_name);
- reference.file_range.range
- }
- };
- SourceFileEdit {
- file_id: reference.file_range.file_id,
- edit: TextEdit::replace(range, replacement_text),
+) -> (FileId, TextEdit) {
+ let mut edit = TextEdit::builder();
+ for reference in references {
+ let mut replacement_text = String::new();
+ let range = match reference.kind {
+ ReferenceKind::FieldShorthandForField => {
+ mark::hit!(test_rename_struct_field_for_shorthand);
+ replacement_text.push_str(new_name);
+ replacement_text.push_str(": ");
+ TextRange::new(reference.range.start(), reference.range.start())
+ }
+ ReferenceKind::FieldShorthandForLocal => {
+ mark::hit!(test_rename_local_for_field_shorthand);
+ replacement_text.push_str(": ");
+ replacement_text.push_str(new_name);
+ TextRange::new(reference.range.end(), reference.range.end())
+ }
+ ReferenceKind::RecordFieldExprOrPat => {
+ mark::hit!(test_rename_field_expr_pat);
+ replacement_text.push_str(new_name);
+ edit_text_range_for_record_field_expr_or_pat(
+ sema,
+ FileRange { file_id, range: reference.range },
+ new_name,
+ )
+ }
+ _ => {
+ replacement_text.push_str(new_name);
+ reference.range
+ }
+ };
+ edit.replace(range, replacement_text);
}
+ (file_id, edit.finish())
}
fn edit_text_range_for_record_field_expr_or_pat(
if IdentifierKind::Ident != check_identifier(new_name)? {
bail!("Invalid name `{0}`: cannot rename module to {0}", new_name);
}
- let mut source_file_edits = Vec::new();
- let mut file_system_edits = Vec::new();
+
+ let mut source_change = SourceChange::default();
let src = module.definition_source(sema.db);
let file_id = src.file_id.original_file(sema.db);
};
let dst = AnchoredPathBuf { anchor: file_id, path };
let move_file = FileSystemEdit::MoveFile { src: file_id, dst };
- file_system_edits.push(move_file);
+ source_change.push_file_system_edit(move_file);
}
ModuleSource::Module(..) => {}
}
if let Some(src) = module.declaration_source(sema.db) {
let file_id = src.file_id.original_file(sema.db);
let name = src.value.name().unwrap();
- let edit = SourceFileEdit {
+ source_change.insert_source_edit(
file_id,
- edit: TextEdit::replace(name.syntax().text_range(), new_name.into()),
- };
- source_file_edits.push(edit);
+ TextEdit::replace(name.syntax().text_range(), new_name.into()),
+ );
}
let RangeInfo { range, info: refs } = find_all_refs(sema, position)?;
- let ref_edits = refs
- .references
- .into_iter()
- .map(|reference| source_edit_from_reference(sema, reference, new_name));
- source_file_edits.extend(ref_edits);
+ let ref_edits = refs.references().iter().map(|(&file_id, references)| {
+ source_edit_from_references(sema, file_id, references, new_name)
+ });
+ source_change.extend(ref_edits);
- Ok(RangeInfo::new(range, SourceChange::from_edits(source_file_edits, file_system_edits)))
+ Ok(RangeInfo::new(range, source_change))
}
fn rename_to_self(
let RangeInfo { range, info: refs } = find_all_refs(sema, position)?;
- let (param_ref, usages): (Vec<Reference>, Vec<Reference>) = refs
- .into_iter()
- .partition(|reference| param_range.intersect(reference.file_range.range).is_some());
-
- if param_ref.is_empty() {
- bail!("Parameter to rename not found");
- }
-
- let mut edits = usages
- .into_iter()
- .map(|reference| source_edit_from_reference(sema, reference, "self"))
- .collect::<Vec<_>>();
-
- edits.push(SourceFileEdit {
- file_id: position.file_id,
- edit: TextEdit::replace(param_range, String::from(self_param)),
- });
+ let mut source_change = SourceChange::default();
+ source_change.extend(refs.references().iter().map(|(&file_id, references)| {
+ source_edit_from_references(sema, file_id, references, "self")
+ }));
+ source_change.insert_source_edit(
+ position.file_id,
+ TextEdit::replace(param_range, String::from(self_param)),
+ );
- Ok(RangeInfo::new(range, SourceChange::from(edits)))
+ Ok(RangeInfo::new(range, source_change))
}
fn text_edit_from_self_param(
fn rename_self_to_param(
sema: &Semantics<RootDatabase>,
position: FilePosition,
- self_token: SyntaxToken,
new_name: &str,
+ ident_kind: IdentifierKind,
+ range: TextRange,
+ refs: ReferenceSearchResult,
) -> Result<RangeInfo<SourceChange>, RenameError> {
- let ident_kind = check_identifier(new_name)?;
match ident_kind {
IdentifierKind::Lifetime => bail!("Invalid name `{}`: not an identifier", new_name),
IdentifierKind::ToSelf => {
// no-op
mark::hit!(rename_self_to_self);
- return Ok(RangeInfo::new(self_token.text_range(), SourceChange::default()));
+ return Ok(RangeInfo::new(range, SourceChange::default()));
}
_ => (),
}
let source_file = sema.parse(position.file_id);
let syn = source_file.syntax();
- let text = sema.db.file_text(position.file_id);
let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset)
.ok_or_else(|| format_err!("No surrounding method declaration found"))?;
- let search_range = fn_def.syntax().text_range();
- let mut edits: Vec<SourceFileEdit> = vec![];
+ let mut source_change = SourceChange::default();
+ if let Some(self_param) = fn_def.param_list().and_then(|it| it.self_param()) {
+ if self_param
+ .syntax()
+ .text_range()
+ .contains_range(refs.declaration().nav.focus_or_full_range())
+ {
+ let edit = text_edit_from_self_param(syn, &self_param, new_name)
+ .ok_or_else(|| format_err!("No target type found"))?;
+ source_change.insert_source_edit(position.file_id, edit);
+
+ source_change.extend(refs.references().iter().map(|(&file_id, references)| {
+ source_edit_from_references(sema, file_id, &references, new_name)
+ }));
+
+ if source_change.source_file_edits.len() > 1 && ident_kind == IdentifierKind::Underscore
+ {
+ bail!("Cannot rename reference to `_` as it is being referenced multiple times");
+ }
- for (idx, _) in text.match_indices("self") {
- let offset: TextSize = idx.try_into().unwrap();
- if !search_range.contains_inclusive(offset) {
- continue;
- }
- if let Some(ref usage) = syn.token_at_offset(offset).find(|t| t.kind() == T![self]) {
- let edit = if let Some(ref self_param) = ast::SelfParam::cast(usage.parent()) {
- text_edit_from_self_param(syn, self_param, new_name)
- .ok_or_else(|| format_err!("No target type found"))?
- } else {
- TextEdit::replace(usage.text_range(), String::from(new_name))
- };
- edits.push(SourceFileEdit { file_id: position.file_id, edit });
+ return Ok(RangeInfo::new(range, source_change));
}
}
-
- if edits.len() > 1 && ident_kind == IdentifierKind::Underscore {
- bail!("Cannot rename reference to `_` as it is being referenced multiple times");
- }
-
- let range = ast::SelfParam::cast(self_token.parent())
- .map_or(self_token.text_range(), |p| p.syntax().text_range());
-
- Ok(RangeInfo::new(range, SourceChange::from(edits)))
+ Err(format_err!("Method has no self param"))
}
fn rename_reference(
mark::hit!(rename_not_an_ident_ref);
bail!("Invalid name `{}`: not an identifier", new_name)
}
- (IdentifierKind::ToSelf, ReferenceKind::SelfKw) => {
- unreachable!("rename_self_to_param should've been called instead")
+ (_, ReferenceKind::SelfParam) => {
+ mark::hit!(rename_self_to_param);
+ return rename_self_to_param(sema, position, new_name, ident_kind, range, refs);
}
(IdentifierKind::ToSelf, _) => {
mark::hit!(rename_to_self);
(IdentifierKind::Ident, _) | (IdentifierKind::Underscore, _) => mark::hit!(rename_ident),
}
- let edit = refs
- .into_iter()
- .map(|reference| source_edit_from_reference(sema, reference, new_name))
- .collect::<Vec<_>>();
+ let mut source_change = SourceChange::default();
+ source_change.extend(refs.into_iter().map(|(file_id, references)| {
+ source_edit_from_references(sema, file_id, &references, new_name)
+ }));
- Ok(RangeInfo::new(range, SourceChange::from(edit)))
+ Ok(RangeInfo::new(range, source_change))
}
#[cfg(test)]
let mut text_edit_builder = TextEdit::builder();
let mut file_id: Option<FileId> = None;
for edit in source_change.info.source_file_edits {
- file_id = Some(edit.file_id);
- for indel in edit.edit.into_iter() {
+ file_id = Some(edit.0);
+ for indel in edit.1.into_iter() {
text_edit_builder.replace(indel.delete, indel.insert);
}
}
RangeInfo {
range: 4..7,
info: SourceChange {
- source_file_edits: [
- SourceFileEdit {
- file_id: FileId(
- 1,
- ),
- edit: TextEdit {
- indels: [
- Indel {
- insert: "foo2",
- delete: 4..7,
- },
- ],
- },
+ source_file_edits: {
+ FileId(
+ 1,
+ ): TextEdit {
+ indels: [
+ Indel {
+ insert: "foo2",
+ delete: 4..7,
+ },
+ ],
},
- ],
+ },
file_system_edits: [
MoveFile {
src: FileId(
RangeInfo {
range: 11..14,
info: SourceChange {
- source_file_edits: [
- SourceFileEdit {
- file_id: FileId(
- 0,
- ),
- edit: TextEdit {
- indels: [
- Indel {
- insert: "quux",
- delete: 8..11,
- },
- ],
- },
+ source_file_edits: {
+ FileId(
+ 0,
+ ): TextEdit {
+ indels: [
+ Indel {
+ insert: "quux",
+ delete: 8..11,
+ },
+ ],
},
- SourceFileEdit {
- file_id: FileId(
- 2,
- ),
- edit: TextEdit {
- indels: [
- Indel {
- insert: "quux",
- delete: 11..14,
- },
- ],
- },
+ FileId(
+ 2,
+ ): TextEdit {
+ indels: [
+ Indel {
+ insert: "quux",
+ delete: 11..14,
+ },
+ ],
},
- ],
+ },
file_system_edits: [
MoveFile {
src: FileId(
//- /lib.rs
mod fo$0o;
//- /foo/mod.rs
-// emtpy
+// empty
"#,
expect![[r#"
RangeInfo {
range: 4..7,
info: SourceChange {
- source_file_edits: [
- SourceFileEdit {
- file_id: FileId(
- 0,
- ),
- edit: TextEdit {
- indels: [
- Indel {
- insert: "foo2",
- delete: 4..7,
- },
- ],
- },
+ source_file_edits: {
+ FileId(
+ 0,
+ ): TextEdit {
+ indels: [
+ Indel {
+ insert: "foo2",
+ delete: 4..7,
+ },
+ ],
},
- ],
+ },
file_system_edits: [
MoveFile {
src: FileId(
mod outer { mod fo$0o; }
//- /outer/foo.rs
-// emtpy
+// empty
"#,
expect![[r#"
RangeInfo {
range: 16..19,
info: SourceChange {
- source_file_edits: [
- SourceFileEdit {
- file_id: FileId(
- 0,
- ),
- edit: TextEdit {
- indels: [
- Indel {
- insert: "bar",
- delete: 16..19,
- },
- ],
- },
+ source_file_edits: {
+ FileId(
+ 0,
+ ): TextEdit {
+ indels: [
+ Indel {
+ insert: "bar",
+ delete: 16..19,
+ },
+ ],
},
- ],
+ },
file_system_edits: [
MoveFile {
src: FileId(
RangeInfo {
range: 8..11,
info: SourceChange {
- source_file_edits: [
- SourceFileEdit {
- file_id: FileId(
- 1,
- ),
- edit: TextEdit {
- indels: [
- Indel {
- insert: "foo2",
- delete: 8..11,
- },
- ],
- },
+ source_file_edits: {
+ FileId(
+ 0,
+ ): TextEdit {
+ indels: [
+ Indel {
+ insert: "foo2",
+ delete: 27..30,
+ },
+ ],
},
- SourceFileEdit {
- file_id: FileId(
- 0,
- ),
- edit: TextEdit {
- indels: [
- Indel {
- insert: "foo2",
- delete: 27..30,
- },
- ],
- },
+ FileId(
+ 1,
+ ): TextEdit {
+ indels: [
+ Indel {
+ insert: "foo2",
+ delete: 8..11,
+ },
+ ],
},
- ],
+ },
file_system_edits: [
MoveFile {
src: FileId(
#[test]
fn test_owned_self_to_parameter() {
+ mark::check!(rename_self_to_param);
check(
"foo",
r#"