Auto merge of #15940 - pascalkuthe:fix_rename, r=Veykril

ensure renames happen after edit

This is a bugfix for an issue I fould while working on helix. Rust-analyzer currently always sends any filesystem edits (rename/file creation) before any other edits. When renaming a file that is also being edited that would mean that the edit would be discarded and therefore an incomplete/incorrect refactor (or even cause the creation of a new file in helix altough that  is probably a pub on our side).

Example:

* create a module: `mod foo` containing a `pub sturct Bar;`
* reexport the struct uneder a different name in the `foo` module using a *fully qualified path*: `pub use crate::foo::Bar as Bar2`.
* rename the `foo` module to `foo2` using rust-analyzer
* obsereve that the path is not correctly updated (rust-analyer first sends a rename `foo.rs` to `foo2.rs` and then edits `foo.rs` after)

This PR fixes that issue by simply executing all rename operations after all edit operations (while still executing file creation operations first). I also added a testcase similar to the example above.

Relevent excerpt from the LSP standard:

> Since version 3.13.0 a workspace edit can contain resource operations (create, delete or rename files and folders) as well. If resource operations are present clients need to execute the operations in the order in which they are provided. So a workspace edit for example can consist of the following two changes: (1) create file a.txt and (2) a text document edit which insert text into file a.txt. An invalid sequence (e.g. (1) delete file a.txt and (2) insert text into file a.txt) will cause failure of the operation. How the client recovers from the failure is described by the client capability: workspace.workspaceEdit.failureHandling
This commit is contained in:
bors 2023-11-21 09:33:01 +00:00
commit 2e7e8cc7b9
2 changed files with 44 additions and 5 deletions

View File

@ -1,7 +1,7 @@
//! Conversion of rust-analyzer specific types to lsp_types equivalents.
use std::{
iter::once,
path,
mem, path,
sync::atomic::{AtomicU32, Ordering},
};
@ -1123,13 +1123,20 @@ pub(crate) fn snippet_text_document_ops(
pub(crate) fn snippet_workspace_edit(
snap: &GlobalStateSnapshot,
source_change: SourceChange,
mut source_change: SourceChange,
) -> Cancellable<lsp_ext::SnippetWorkspaceEdit> {
let mut document_changes: Vec<lsp_ext::SnippetDocumentChangeOperation> = Vec::new();
for op in source_change.file_system_edits {
let ops = snippet_text_document_ops(snap, op)?;
document_changes.extend_from_slice(&ops);
for op in &mut source_change.file_system_edits {
if let FileSystemEdit::CreateFile { dst, initial_contents } = op {
// replace with a placeholder to avoid cloneing the edit
let op = FileSystemEdit::CreateFile {
dst: dst.clone(),
initial_contents: mem::take(initial_contents),
};
let ops = snippet_text_document_ops(snap, op)?;
document_changes.extend_from_slice(&ops);
}
}
for (file_id, (edit, snippet_edit)) in source_change.source_file_edits {
let edit = snippet_text_document_edit(
@ -1141,6 +1148,12 @@ pub(crate) fn snippet_workspace_edit(
)?;
document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Edit(edit));
}
for op in source_change.file_system_edits {
if !matches!(op, FileSystemEdit::CreateFile { .. }) {
let ops = snippet_text_document_ops(snap, op)?;
document_changes.extend_from_slice(&ops);
}
}
let mut workspace_edit = lsp_ext::SnippetWorkspaceEdit {
changes: None,
document_changes: Some(document_changes),

View File

@ -984,6 +984,11 @@ fn main() {}
//- /src/old_file.rs
//- /src/old_folder/mod.rs
mod nested;
//- /src/old_folder/nested.rs
struct foo;
use crate::old_folder::nested::foo as bar;
//- /src/from_mod/mod.rs
@ -1080,6 +1085,27 @@ fn main() {}
"newText": "new_folder"
}
]
},
{
"textDocument": {
"uri": format!("file://{}", tmp_dir_path.join("src").join("old_folder").join("nested.rs").to_str().unwrap().to_string().replace("C:\\", "/c:/").replace('\\', "/")),
"version": null
},
"edits": [
{
"range": {
"start": {
"line": 1,
"character": 11
},
"end": {
"line": 1,
"character": 21
}
},
"newText": "new_folder"
}
]
}
]
}),