Unnamed repository; edit this file 'description' to name the repository.
-rw-r--r--crates/ide-db/src/rename.rs7
-rw-r--r--crates/ide-diagnostics/src/handlers/incorrect_case.rs2
-rw-r--r--crates/ide/src/lib.rs3
-rw-r--r--crates/ide/src/rename.rs70
-rw-r--r--crates/rust-analyzer/src/config.rs8
-rw-r--r--crates/rust-analyzer/src/handlers/request.rs6
-rw-r--r--docs/user/generated_config.adoc5
-rw-r--r--editors/code/package.json5
8 files changed, 77 insertions, 29 deletions
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<SourceChange> {
// 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<Vec<Ass
let label = format!("Rename to {}", d.suggested_text);
let mut res = unresolved_fix("change_case", &label, frange.range);
if ctx.resolve.should_resolve(&res.id) {
- let source_change = def.rename(&ctx.sema, &d.suggested_text);
+ let source_change = def.rename(&ctx.sema, &d.suggested_text, true);
res.source_change = Some(source_change.ok().unwrap_or_default());
}
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index 60a9367adc..9ad1630225 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -680,8 +680,9 @@ impl Analysis {
&self,
position: FilePosition,
new_name: &str,
+ rename_external: bool,
) -> Cancellable<Result<SourceChange, RenameError>> {
- 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<SourceChange> {
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<FileId> = 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<String> = "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, &params.new_name)?.map_err(to_proto::rename_error)?;
+ let mut change = snap
+ .analysis
+ .rename(position, &params.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,