дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 19:25:54 +02:00
Multi-file refactorings could drop LSP document versions, overwrite content changed after a preview, or leave earlier files saved after a later write failed. Workspace edits now keep server versions and preview fingerprints, are validated on a staging copy, snapshot disk and open buffers, and roll back on commit failure (retaining recovery files if rollback fails). The whole edit is one Undo/Redo step in editors and in the notification. Version reads and transaction work run off the async workers. Closes #461. Co-authored-by: shohruh-genius <[email protected]> Co-Authored-By: Claude Opus 5.5 <[email protected]>
121 рядки
4.2 KiB
Rust
121 рядки
4.2 KiB
Rust
//! File move / rename refactoring: reference updates from the language
|
|
//! servers, the move itself (one undoable step) and its undo.
|
|
use crate::Host;
|
|
use crate::rpc::Dispatcher;
|
|
|
|
use ide_core::{IdeResult, RequestId, WorkspaceEdit};
|
|
use ide_lsp::types::FileRenameEdits;
|
|
use ide_workspace::{FileMove, UndoOutcome};
|
|
use std::path::PathBuf;
|
|
|
|
use super::doc::blocking;
|
|
|
|
fn pairs(moves: &[FileMove]) -> Vec<(PathBuf, PathBuf)> {
|
|
moves.iter().map(|m| (m.from.to_path_buf(), m.to.to_path_buf())).collect()
|
|
}
|
|
|
|
/// `workspace/willRenameFiles` on every interested server (before the move). Cancellable.
|
|
pub async fn refactor_file_move_edits(
|
|
host: &Host,
|
|
window: &str,
|
|
request: RequestId,
|
|
moves: Vec<FileMove>,
|
|
) -> IdeResult<FileRenameEdits> {
|
|
let project = host.project(window)?;
|
|
let token = host.begin_request(request);
|
|
let result = project.lsp.will_rename_files(&pairs(&moves), token).await;
|
|
host.end_request(request);
|
|
let mut result = result?;
|
|
blocking(move || {
|
|
project.documents.prepare_workspace_edit(&mut result.edit)?;
|
|
Ok(result)
|
|
})
|
|
.await
|
|
}
|
|
|
|
/// Applies `edit` (from [`refactor_file_move_edits`], possibly empty), moves the
|
|
/// files and notifies the servers. Returns the id for [`refactor_undo_file_move`].
|
|
pub async fn refactor_move_files(
|
|
host: &Host,
|
|
window: &str,
|
|
moves: Vec<FileMove>,
|
|
edit: WorkspaceEdit,
|
|
label: String,
|
|
) -> IdeResult<u32> {
|
|
let project = host.project(window)?;
|
|
let p = project.clone();
|
|
let moved = moves.clone();
|
|
let id = blocking(move || {
|
|
let apply = |e: &WorkspaceEdit| p.documents.apply_file_move_edits(e);
|
|
p.moves.perform(&p.documents, Some(&p.history), &moved, &edit, &label, &apply)
|
|
})
|
|
.await?;
|
|
project.lsp.did_rename_files(&pairs(&moves));
|
|
Ok(id)
|
|
}
|
|
|
|
/// Undoes a file move: moves back and reverts the reference updates.
|
|
pub async fn refactor_undo_file_move(host: &Host, window: &str, id: u32) -> IdeResult<UndoOutcome> {
|
|
let project = host.project(window)?;
|
|
let p = project.clone();
|
|
let outcome = blocking(move || p.moves.undo(&p.documents, id)).await?;
|
|
project.lsp.did_rename_files(&pairs(&outcome.moves));
|
|
Ok(outcome)
|
|
}
|
|
|
|
/// Whether the file move `id` can still be undone.
|
|
pub fn refactor_can_undo_file_move(host: &Host, window: &str, id: u32) -> IdeResult<bool> {
|
|
Ok(host.project(window)?.moves.can_undo(id))
|
|
}
|
|
|
|
/// "Search in comments and strings": textual occurrences of the moved files'
|
|
/// names, qualified names and paths (before the move). Cancellable.
|
|
pub async fn refactor_file_move_text_edits(
|
|
host: &Host,
|
|
window: &str,
|
|
request: RequestId,
|
|
moves: Vec<FileMove>,
|
|
) -> IdeResult<WorkspaceEdit> {
|
|
let project = host.project(window)?;
|
|
let token = host.begin_request(request);
|
|
let cancel = token.clone();
|
|
let result = blocking(move || {
|
|
let replacements = ide_workspace::text_refs::move_replacements(&project.root, &project.roots(), &moves);
|
|
let mut edit = ide_workspace::text_refs::find_text_occurrences(
|
|
&project.root,
|
|
&project.exclude_globs(),
|
|
&replacements,
|
|
&|| cancel.is_cancelled(),
|
|
);
|
|
if cancel.is_cancelled() {
|
|
return Err(ide_core::IdeError::cancelled());
|
|
}
|
|
project.documents.prepare_workspace_edit(&mut edit)?;
|
|
Ok(edit)
|
|
})
|
|
.await;
|
|
host.end_request(request);
|
|
result
|
|
}
|
|
|
|
/// Commands of this module the remote server answers.
|
|
pub(crate) fn register(d: &mut Dispatcher) {
|
|
d.add("refactor_file_move_edits", |c| async move {
|
|
refactor_file_move_edits(&c.host, &c.window, c.arg("request")?, c.arg("moves")?).await
|
|
});
|
|
d.add("refactor_move_files", |c| async move {
|
|
refactor_move_files(&c.host, &c.window, c.arg("moves")?, c.arg("edit")?, c.arg("label")?).await
|
|
});
|
|
d.add(
|
|
"refactor_undo_file_move",
|
|
|c| async move { refactor_undo_file_move(&c.host, &c.window, c.arg("id")?).await },
|
|
);
|
|
d.add(
|
|
"refactor_can_undo_file_move",
|
|
|c| async move { refactor_can_undo_file_move(&c.host, &c.window, c.arg("id")?) },
|
|
);
|
|
d.add("refactor_file_move_text_edits", |c| async move {
|
|
refactor_file_move_text_edits(&c.host, &c.window, c.arg("request")?, c.arg("moves")?).await
|
|
});
|
|
}
|