diff --git a/crates/ide-db/src/rename.rs b/crates/ide-db/src/rename.rs index f694f7160d..a6058b57f1 100644 --- a/crates/ide-db/src/rename.rs +++ b/crates/ide-db/src/rename.rs @@ -71,14 +71,17 @@ impl Definition { &self, sema: &Semantics<'_, RootDatabase>, new_name: &str, + rename_external: bool, ) -> Result { // self.krate() returns None if // self is a built-in attr, built-in type or tool module. // it is not allowed for these defs to be renamed. // cases where self.krate() is None is handled below. if let Some(krate) = self.krate(sema.db) { - if !krate.origin(sema.db).is_local() { - bail!("Cannot rename a non-local definition.") + // Can we not rename non-local items? + // Then bail if non-local + if !rename_external && !krate.origin(sema.db).is_local() { + bail!("Cannot rename a non-local definition. Set `renameExternalItems` to `true` to allow renaming for this item.") } } diff --git a/crates/ide-diagnostics/src/handlers/incorrect_case.rs b/crates/ide-diagnostics/src/handlers/incorrect_case.rs index 0f12e814ba..25a5cdde26 100644 --- a/crates/ide-diagnostics/src/handlers/incorrect_case.rs +++ b/crates/ide-diagnostics/src/handlers/incorrect_case.rs @@ -43,7 +43,7 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::IncorrectCase) -> Option Cancellable> { - self.with_db(|db| rename::rename(db, position, new_name)) + self.with_db(|db| rename::rename(db, position, new_name, rename_external)) } pub fn prepare_rename( diff --git a/crates/ide/src/rename.rs b/crates/ide/src/rename.rs index 3bf41defe3..0446b4a4b6 100644 --- a/crates/ide/src/rename.rs +++ b/crates/ide/src/rename.rs @@ -84,6 +84,7 @@ pub(crate) fn rename( db: &RootDatabase, position: FilePosition, new_name: &str, + rename_external: bool, ) -> RenameResult { let sema = Semantics::new(db); let source_file = sema.parse(position.file_id); @@ -103,7 +104,7 @@ pub(crate) fn rename( return rename_to_self(&sema, local); } } - def.rename(&sema, new_name) + def.rename(&sema, new_name, rename_external) }) .collect(); @@ -122,9 +123,9 @@ pub(crate) fn will_rename_file( let module = sema.to_module_def(file_id)?; let def = Definition::Module(module); let mut change = if is_raw_identifier(new_name_stem) { - def.rename(&sema, &SmolStr::from_iter(["r#", new_name_stem])).ok()? + def.rename(&sema, &SmolStr::from_iter(["r#", new_name_stem]), true).ok()? } else { - def.rename(&sema, new_name_stem).ok()? + def.rename(&sema, new_name_stem, true).ok()? }; change.file_system_edits.clear(); Some(change) @@ -371,12 +372,21 @@ mod tests { use test_utils::assert_eq_text; use text_edit::TextEdit; - use crate::{fixture, FileId}; + use crate::fixture; use super::{RangeInfo, RenameError}; - #[track_caller] fn check(new_name: &str, ra_fixture_before: &str, ra_fixture_after: &str) { + check_with_rename_config(new_name, ra_fixture_before, ra_fixture_after, true); + } + + #[track_caller] + fn check_with_rename_config( + new_name: &str, + ra_fixture_before: &str, + ra_fixture_after: &str, + rename_external: bool, + ) { let ra_fixture_after = &trim_indent(ra_fixture_after); let (analysis, position) = fixture::position(ra_fixture_before); if !ra_fixture_after.starts_with("error: ") { @@ -385,23 +395,22 @@ mod tests { } } let rename_result = analysis - .rename(position, new_name) + .rename(position, new_name, rename_external) .unwrap_or_else(|err| panic!("Rename to '{new_name}' was cancelled: {err}")); match rename_result { Ok(source_change) => { let mut text_edit_builder = TextEdit::builder(); - let mut file_id: Option = None; - for edit in source_change.source_file_edits { - file_id = Some(edit.0); - for indel in edit.1 .0.into_iter() { - text_edit_builder.replace(indel.delete, indel.insert); - } - } - if let Some(file_id) = file_id { - let mut result = analysis.file_text(file_id).unwrap().to_string(); - text_edit_builder.finish().apply(&mut result); - assert_eq_text!(ra_fixture_after, &*result); + let (&file_id, edit) = match source_change.source_file_edits.len() { + 0 => return, + 1 => source_change.source_file_edits.iter().next().unwrap(), + _ => (&position.file_id, &source_change.source_file_edits[&position.file_id]), + }; + for indel in edit.0.iter() { + text_edit_builder.replace(indel.delete, indel.insert.clone()); } + let mut result = analysis.file_text(file_id).unwrap().to_string(); + text_edit_builder.finish().apply(&mut result); + assert_eq_text!(ra_fixture_after, &*result); } Err(err) => { if ra_fixture_after.starts_with("error:") { @@ -417,8 +426,10 @@ mod tests { fn check_expect(new_name: &str, ra_fixture: &str, expect: Expect) { let (analysis, position) = fixture::position(ra_fixture); - let source_change = - analysis.rename(position, new_name).unwrap().expect("Expect returned a RenameError"); + let source_change = analysis + .rename(position, new_name, true) + .unwrap() + .expect("Expect returned a RenameError"); expect.assert_eq(&filter_expect(source_change)) } @@ -2617,6 +2628,18 @@ use qux as frob; #[test] fn disallow_renaming_for_non_local_definition() { + check_with_rename_config( + "Baz", + r#" +//- /lib.rs crate:lib new_source_root:library +pub struct S; +//- /main.rs crate:main deps:lib new_source_root:local +use lib::S$0; +"#, + "error: Cannot rename a non-local definition. Set `renameExternalItems` to `true` to allow renaming for this item.", + false, + ); + check( "Baz", r#" @@ -2625,13 +2648,13 @@ pub struct S; //- /main.rs crate:main deps:lib new_source_root:local use lib::S$0; "#, - "error: Cannot rename a non-local definition.", + "use lib::Baz;\n", ); } #[test] fn disallow_renaming_for_builtin_macros() { - check( + check_with_rename_config( "Baz", r#" //- minicore: derive, hash @@ -2640,8 +2663,9 @@ use core::hash::Hash; #[derive(H$0ash)] struct A; "#, - "error: Cannot rename a non-local definition.", - ) + "error: Cannot rename a non-local definition. Set `renameExternalItems` to `true` to allow renaming for this item.", + false, + ); } #[test] diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 3ec5d86966..75229a4d06 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -494,6 +494,10 @@ config_data! { /// Exclude imports from find-all-references. references_excludeImports: bool = "false", + /// Allow renaming of items not belonging to the loaded workspaces. + rename_allowExternalItems: bool = "false", + + /// Command to be executed instead of 'cargo' for runnables. runnables_command: Option = "null", /// Additional arguments to be passed to cargo for runnables such as @@ -1739,6 +1743,10 @@ impl Config { self.data.typing_autoClosingAngleBrackets_enable } + pub fn rename(&self) -> bool { + self.data.rename_allowExternalItems + } + // FIXME: VSCode seems to work wrong sometimes, see https://github.com/microsoft/vscode/issues/193124 // hence, distinguish it for now. pub fn is_visual_studio_code(&self) -> bool { diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs index 22c7e9b050..349e6e5023 100644 --- a/crates/rust-analyzer/src/handlers/request.rs +++ b/crates/rust-analyzer/src/handlers/request.rs @@ -1017,8 +1017,10 @@ pub(crate) fn handle_rename( let _p = profile::span("handle_rename"); let position = from_proto::file_position(&snap, params.text_document_position)?; - let mut change = - snap.analysis.rename(position, ¶ms.new_name)?.map_err(to_proto::rename_error)?; + let mut change = snap + .analysis + .rename(position, ¶ms.new_name, snap.config.rename())? + .map_err(to_proto::rename_error)?; // this is kind of a hack to prevent double edits from happening when moving files // When a module gets renamed by renaming the mod declaration this causes the file to move diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc index 1a2791954e..f887bb9df3 100644 --- a/docs/user/generated_config.adoc +++ b/docs/user/generated_config.adoc @@ -777,6 +777,11 @@ Internal config, path to proc-macro server executable. -- Exclude imports from find-all-references. -- +[[rust-analyzer.rename.allowExternalItems]]rust-analyzer.rename.allowExternalItems (default: `false`):: ++ +-- +Allow renaming of items not belonging to the loaded workspaces. +-- [[rust-analyzer.runnables.command]]rust-analyzer.runnables.command (default: `null`):: + -- diff --git a/editors/code/package.json b/editors/code/package.json index e7ceee165c..58b7da921a 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -1503,6 +1503,11 @@ "default": false, "type": "boolean" }, + "rust-analyzer.rename.allowExternalItems": { + "markdownDescription": "Allow renaming of items not belonging to the loaded workspaces.", + "default": false, + "type": "boolean" + }, "rust-analyzer.runnables.command": { "markdownDescription": "Command to be executed instead of 'cargo' for runnables.", "default": null,