fix(refactor): apply server-versioned edits to files that aren't open (#1295)

gopls sends a document version for every file of a rename, including files
the editor never opened. Applying a workspace edit rejected any versioned
file without an open document as changed, so a Go rename touching a closed
file always failed with "changed; request the refactoring again". For a file
that isn't open, the preconditions captured with the edit now decide; an
edit without them is still rejected.
Цей коміт міститься в:
l0ng-ai authored and GitHub committed 2026-11-04 23:32:51 +08:00
1 джерело 6f9819fee7
коміт f35c781f42
2 files changed
+39 -3

No files matched your search

+8 -3
Переглянути файл
@@ -402,13 +402,18 @@ impl Documents {
return Ok(());
}
let mut edit = normalize_edit(edit)?;
let captured = !edit.preconditions.is_empty();
self.prepare_workspace_edit(&mut edit)?;
self.validate_preconditions(&edit.preconditions)?;
for file in &edit.files {
if let FileEdit::Edit { path, version: Some(version), .. } = file {
let doc = self.by_path(path).ok_or_else(|| stale(path))?;
if i64::from(self.version(doc)?) != i64::from(*version) {
return Err(stale(path));
match self.by_path(path) {
Some(doc) if i64::from(self.version(doc)?) != i64::from(*version) => return Err(stale(path)),
Some(_) => {}
// Servers version files the editor never opened (gopls renames). The
// preconditions captured with the edit already cover its disk contents.
None if captured => {}
None => return Err(stale(path)),
}
}
}
+31
Переглянути файл
@@ -111,6 +111,37 @@ fn matching_document_version_is_applied() {
assert_eq!(std::fs::read_to_string(a).unwrap(), "new\n");
}
#[test]
fn versioned_edit_of_a_closed_file_is_guarded_by_its_preconditions() {
// gopls versions every file of a rename, including ones the editor never opened.
let tmp = tempfile::tempdir().unwrap();
let path = tmp.path().join("closed.txt");
std::fs::write(&path, "old\n").unwrap();
let docs = documents();
let versioned = || {
let mut operation = edit(&path, "new");
if let FileEdit::Edit { version, .. } = &mut operation {
*version = Some(0);
}
WorkspaceEdit { files: vec![operation], ..Default::default() }
};
// Without a snapshot of what the server saw, a version for a closed file can't be trusted.
assert_eq!(docs.apply_workspace_edit(&versioned()).unwrap_err().kind, ErrorKind::OutOfSync);
assert_eq!(std::fs::read_to_string(&path).unwrap(), "old\n");
let mut changed = versioned();
docs.prepare_workspace_edit(&mut changed).unwrap();
std::fs::write(&path, "own\n").unwrap();
assert_eq!(docs.apply_workspace_edit(&changed).unwrap_err().kind, ErrorKind::OutOfSync);
assert_eq!(std::fs::read_to_string(&path).unwrap(), "own\n");
std::fs::write(&path, "old\n").unwrap();
let mut prepared = versioned();
docs.prepare_workspace_edit(&mut prepared).unwrap();
docs.apply_workspace_edit(&prepared).unwrap();
assert_eq!(std::fs::read_to_string(&path).unwrap(), "new\n");
}
#[test]
fn unversioned_preview_rejects_changed_disk_and_dirty_buffers() {
let tmp = tempfile::tempdir().unwrap();