From 3c7a1ede55f4cf9d7e07a9c723f84efb1355a8d2 Mon Sep 17 00:00:00 2001 From: Lars Vogel Date: Thu, 11 Jun 2026 19:14:07 +0200 Subject: [PATCH 1/2] Refresh out-of-sync resources in DeleteResourcesProcessor When the workspace "Refresh on access" preference (Preferences > General > Workspace > "Refresh on access", backed by ResourcesPlugin.PREF_LIGHTWEIGHT_AUTO_REFRESH) is enabled, the Delete Resources refactoring wizard reports "is not in sync with" warnings even though the user explicitly opted into refresh on access. Note that this preference is enabled by default. DeleteResourcesProcessor now follows the same pattern as RenameResourceProcessor: if the sync check fails and the preference is enabled, the resource is refreshed and the check is repeated. Resources that are already in sync are not refreshed. If the refresh reveals that a resource was deleted externally, the resource is dropped from the set to delete instead of failing later in DeleteResourceChange with "resource does not exist". The refresh runs under a child progress monitor so it stays cancelable, and a failed refresh degrades to the existing out-of-sync warning rather than aborting the condition check. Fixes https://github.com/eclipse-platform/eclipse.platform.ui/issues/3982 --- .../resource/DeleteResourcesProcessor.java | 29 +++++- .../resource/ResourceRefactoringTests.java | 90 +++++++++++++++++++ 2 files changed, 117 insertions(+), 2 deletions(-) diff --git a/bundles/org.eclipse.ltk.core.refactoring/src/org/eclipse/ltk/internal/core/refactoring/resource/DeleteResourcesProcessor.java b/bundles/org.eclipse.ltk.core.refactoring/src/org/eclipse/ltk/internal/core/refactoring/resource/DeleteResourcesProcessor.java index c77f5e1e9d8..598e49af19b 100644 --- a/bundles/org.eclipse.ltk.core.refactoring/src/org/eclipse/ltk/internal/core/refactoring/resource/DeleteResourcesProcessor.java +++ b/bundles/org.eclipse.ltk.core.refactoring/src/org/eclipse/ltk/internal/core/refactoring/resource/DeleteResourcesProcessor.java @@ -16,11 +16,14 @@ import java.net.URI; import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.SubMonitor; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; @@ -28,6 +31,7 @@ import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceVisitor; +import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.resources.mapping.IResourceChangeDescriptionFactory; import org.eclipse.core.filebuffers.FileBuffers; @@ -120,12 +124,30 @@ public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws Core @Override public RefactoringStatus checkFinalConditions(IProgressMonitor pm, CheckConditionsContext context) throws CoreException, OperationCanceledException { - pm.beginTask("", 1); //$NON-NLS-1$ + SubMonitor subMonitor= SubMonitor.convert(pm, fResources.length); try { RefactoringStatus result= new RefactoringStatus(); + boolean lightweightAutoRefresh= Platform.getPreferencesService().getBoolean(ResourcesPlugin.PI_RESOURCES, + ResourcesPlugin.PREF_LIGHTWEIGHT_AUTO_REFRESH, false, null); + + List remainingResources= new ArrayList<>(fResources.length); for (IResource resource : fResources) { - if (!isSynchronizedExcludingLinkedResources(resource)) { + boolean inSync= isSynchronizedExcludingLinkedResources(resource); + if (!inSync && lightweightAutoRefresh && resource.isAccessible()) { + try { + resource.refreshLocal(IResource.DEPTH_INFINITE, subMonitor.split(1)); + } catch (CoreException e) { + // refresh failed; the out-of-sync warning below covers it + } + if (!resource.exists()) { + // deleted externally; the refresh already removed it from the workspace + continue; + } + inSync= isSynchronizedExcludingLinkedResources(resource); + } + remainingResources.add(resource); + if (!inSync) { String pathLabel= BasicElementLabels.getPathLabel(resource.getFullPath(), false); String locationLabel= null; @@ -156,6 +178,9 @@ public RefactoringStatus checkFinalConditions(IProgressMonitor pm, CheckConditio result.addWarning(warning); } } + if (remainingResources.size() != fResources.length) { + fResources= remainingResources.toArray(IResource[]::new); + } checkDirtyResources(result); diff --git a/tests/org.eclipse.ltk.core.refactoring.tests/src/org/eclipse/ltk/core/refactoring/tests/resource/ResourceRefactoringTests.java b/tests/org.eclipse.ltk.core.refactoring.tests/src/org/eclipse/ltk/core/refactoring/tests/resource/ResourceRefactoringTests.java index 8b075f415a7..dbe418d1554 100644 --- a/tests/org.eclipse.ltk.core.refactoring.tests/src/org/eclipse/ltk/core/refactoring/tests/resource/ResourceRefactoringTests.java +++ b/tests/org.eclipse.ltk.core.refactoring.tests/src/org/eclipse/ltk/core/refactoring/tests/resource/ResourceRefactoringTests.java @@ -19,7 +19,9 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.io.File; import java.io.IOException; +import java.nio.file.Files; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -29,6 +31,8 @@ import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.preferences.InstanceScope; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; @@ -66,6 +70,7 @@ public void setUp() throws Exception { @AfterEach public void tearDown() throws Exception { + InstanceScope.INSTANCE.getNode(ResourcesPlugin.PI_RESOURCES).remove(ResourcesPlugin.PREF_LIGHTWEIGHT_AUTO_REFRESH); fProject.delete(); } @@ -392,6 +397,65 @@ public void testDeleteRefactoring3_bug343584() throws Exception { } } + @Test + public void testDeleteRefactoringOutOfSync_noAutoRefresh() throws Exception { + setLightweightAutoRefresh(false); + IFolder testFolder= fProject.createFolder("test"); + IFile file= fProject.createFile(testFolder, "myFile.txt", "hello"); + modifyExternally(file); + + RefactoringContext context= createDeleteRefactoringContext(file); + try { + RefactoringStatus status= context.getRefactoring().checkAllConditions(new NullProgressMonitor()); + assertTrue(status.hasWarning(), "expected an out-of-sync warning"); + } finally { + context.dispose(); + } + } + + @Test + public void testDeleteRefactoringOutOfSync_autoRefresh() throws Exception { + setLightweightAutoRefresh(true); + IFolder testFolder= fProject.createFolder("test"); + IFile file= fProject.createFile(testFolder, "myFile.txt", "hello"); + File localFile= modifyExternally(file); + + RefactoringContext context= createDeleteRefactoringContext(file); + try { + Refactoring refactoring= context.getRefactoring(); + RefactoringStatus status= refactoring.checkAllConditions(new NullProgressMonitor()); + assertTrue(status.isOK(), () -> "expected no warning but was: " + status); + + perform(refactoring.createChange(new NullProgressMonitor())); + + assertFalse(file.exists()); + assertFalse(localFile.exists()); + } finally { + context.dispose(); + } + } + + @Test + public void testDeleteRefactoringDeletedExternally_autoRefresh() throws Exception { + setLightweightAutoRefresh(true); + IFolder testFolder= fProject.createFolder("test"); + IFile file= fProject.createFile(testFolder, "myFile.txt", "hello"); + Files.delete(file.getLocation().toFile().toPath()); + + RefactoringContext context= createDeleteRefactoringContext(file); + try { + Refactoring refactoring= context.getRefactoring(); + RefactoringStatus status= refactoring.checkAllConditions(new NullProgressMonitor()); + assertTrue(status.isOK(), () -> "expected no warning but was: " + status); + + perform(refactoring.createChange(new NullProgressMonitor())); + + assertFalse(file.exists()); + } finally { + context.dispose(); + } + } + @Test public void testCopyProjectRefactoring() throws Exception { String content1= "hello"; @@ -419,6 +483,32 @@ public void testCopyProjectRefactoring() throws Exception { assertFalse(targetProject.exists()); } + private void setLightweightAutoRefresh(boolean enabled) { + InstanceScope.INSTANCE.getNode(ResourcesPlugin.PI_RESOURCES).putBoolean(ResourcesPlugin.PREF_LIGHTWEIGHT_AUTO_REFRESH, enabled); + } + + /** + * Modifies the file in the local file system without notifying the workspace, so the file is + * out of sync afterwards. + */ + private File modifyExternally(IFile file) throws IOException { + File localFile= file.getLocation().toFile(); + Files.writeString(localFile.toPath(), "external change"); + assertTrue(localFile.setLastModified(localFile.lastModified() + 5000)); + assertFalse(file.isSynchronized(IResource.DEPTH_ZERO)); + return localFile; + } + + private RefactoringContext createDeleteRefactoringContext(IResource... resources) throws CoreException { + RefactoringContribution contribution= RefactoringCore.getRefactoringContribution(DeleteResourcesDescriptor.ID); + DeleteResourcesDescriptor descriptor= (DeleteResourcesDescriptor) contribution.createDescriptor(); + descriptor.setResources(resources); + RefactoringStatus status= new RefactoringStatus(); + RefactoringContext context= descriptor.createRefactoringContext(status); + assertTrue(status.isOK()); + return context; + } + private Change perform(Change change) throws CoreException { PerformChangeOperation op= new PerformChangeOperation(change); op.run(null); From 6e53810a12aa33cd889e5bf2a85804930c254511 Mon Sep 17 00:00:00 2001 From: Eclipse Platform Bot Date: Thu, 11 Jun 2026 17:25:25 +0000 Subject: [PATCH 2/2] Version bump(s) for 4.41 stream --- bundles/org.eclipse.ltk.core.refactoring/META-INF/MANIFEST.MF | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.eclipse.ltk.core.refactoring/META-INF/MANIFEST.MF b/bundles/org.eclipse.ltk.core.refactoring/META-INF/MANIFEST.MF index 9f0f659919b..0933dc953b7 100644 --- a/bundles/org.eclipse.ltk.core.refactoring/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.ltk.core.refactoring/META-INF/MANIFEST.MF @@ -3,7 +3,7 @@ Automatic-Module-Name: org.eclipse.ltk.core.refactoring Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.ltk.core.refactoring; singleton:=true -Bundle-Version: 3.15.200.qualifier +Bundle-Version: 3.15.300.qualifier Bundle-Activator: org.eclipse.ltk.internal.core.refactoring.RefactoringCorePlugin Bundle-ActivationPolicy: lazy Bundle-Vendor: %providerName