From be83d0871cf211839074b9372c1cc88bc95e586c Mon Sep 17 00:00:00 2001 From: Mehmet Emin Karaman Date: Mon, 22 Jul 2024 12:44:22 +0200 Subject: [PATCH] Fix editor sync bug, when model file was edited outside of eclipse. This bugfix contains the fix for unsync workspace files wrt. the file system (see the DirtyStateEditorSupportIntegrationTest.testModifyFileInExternEditor) and a one liner for beeing able to save the file when the changes were ignored. (see the ignored test case DirtyStateEditorSupportIntegrationTest.testModifyDirtyFileInExternEditor). The issue for this fix can be found here: https://github.com/eclipse/xtext/issues/2385 --- ...irtyStateEditorSupportIntegrationTest.java | 62 +++++++++++++++++++ .../eclipse/xtext/ui/editor/XtextEditor.java | 12 ++++ .../editor/model/XtextDocumentProvider.java | 19 +++++- 3 files changed, 92 insertions(+), 1 deletion(-) diff --git a/org.eclipse.xtext.ui.tests/src-longrunning/org/eclipse/xtext/ui/tests/editor/DirtyStateEditorSupportIntegrationTest.java b/org.eclipse.xtext.ui.tests/src-longrunning/org/eclipse/xtext/ui/tests/editor/DirtyStateEditorSupportIntegrationTest.java index 7aff6f28075..6d87ae4b2c0 100644 --- a/org.eclipse.xtext.ui.tests/src-longrunning/org/eclipse/xtext/ui/tests/editor/DirtyStateEditorSupportIntegrationTest.java +++ b/org.eclipse.xtext.ui.tests/src-longrunning/org/eclipse/xtext/ui/tests/editor/DirtyStateEditorSupportIntegrationTest.java @@ -8,28 +8,35 @@ */ package org.eclipse.xtext.ui.tests.editor; +import java.io.File; +import java.io.FileWriter; import java.util.ArrayList; import java.util.List; import java.util.Objects; import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.jobs.Job; import org.eclipse.emf.common.util.URI; import org.eclipse.jface.text.IUndoManager; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; +import org.eclipse.ui.IFileEditorInput; import org.eclipse.xtext.resource.IEObjectDescription; import org.eclipse.xtext.resource.IResourceDescription; import org.eclipse.xtext.resource.IResourceServiceProvider; import org.eclipse.xtext.ui.editor.XtextEditor; import org.eclipse.xtext.ui.editor.XtextSourceViewer; +import org.eclipse.xtext.ui.editor.model.IXtextDocument; import org.eclipse.xtext.ui.refactoring.ui.SyncUtil; import org.eclipse.xtext.ui.testing.AbstractEditorTest; import org.eclipse.xtext.ui.testing.util.IResourcesSetupUtil; import org.junit.Assert; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import com.google.common.collect.Iterables; @@ -116,7 +123,62 @@ public void testUndoRedo() throws Exception { .getQualifiedName().getLastSegment()); Iterables.getLast(events); } + + /* + * @see https://github.com/eclipse/xtext/issues/2385 + */ + @Test public void testModifyFileInExternEditor() throws Exception { + IXtextDocument document = editor.getDocument(); + + Display.getDefault().readAndDispatch(); + + assertNotEquals(document.get(), ""); + + getWorkbench().getActiveWorkbenchWindow().getActivePage().getViewReferences()[0].getView(false).setFocus(); + + File externalEditFile = ((IFileEditorInput)editor.getEditorInput()).getFile().getLocation().toFile(); + try (FileWriter fw = new FileWriter(externalEditFile)) { + fw.write(""); + } + editor.setFocus(); + Job.getJobManager().join(ResourcesPlugin.FAMILY_AUTO_REFRESH, null); + syncUtil.yieldToQueuedDisplayJobs(new NullProgressMonitor()); + syncUtil.waitForReconciler(editor); + assertEquals(document.get(), ""); + } + + /** + * This test needs a manual button click. + */ + @Ignore + @Test public void testModifyDirtyFileInExternEditor() throws Exception { + IXtextDocument document = editor.getDocument(); + Display.getDefault().readAndDispatch(); + + assertNotEquals(document.get(), ""); + document.set("/* Hello World! */"); + assertEquals("/* Hello World! */", document.get()); + assertTrue(editor.isDirty()); + + getWorkbench().getActiveWorkbenchWindow().getActivePage().getViewReferences()[0].getView(false).setFocus(); + + File externalEditFile = ((IFileEditorInput)editor.getEditorInput()).getFile().getLocation().toFile(); + try (FileWriter fw = new FileWriter(externalEditFile)) { + fw.write(""); + } + editor.setFocus(); + // Dialog opens and the "Ignore file change" button has to be clicked. + Job.getJobManager().join(ResourcesPlugin.FAMILY_AUTO_REFRESH, null); + syncUtil.yieldToQueuedDisplayJobs(new NullProgressMonitor()); + syncUtil.waitForReconciler(editor); + assertEquals(document.get(), "/* Hello World! */"); + + assertTrue(editor.isDirty()); + editor.doSave(new NullProgressMonitor()); + assertFalse(editor.isDirty()); + } + private IEObjectDescription getFirstExportedObjectInLastEventDelta() { return Iterables.getFirst(Iterables.getLast(events).getDeltas().get(0).getNew().getExportedObjects(), null); } diff --git a/org.eclipse.xtext.ui/src/org/eclipse/xtext/ui/editor/XtextEditor.java b/org.eclipse.xtext.ui/src/org/eclipse/xtext/ui/editor/XtextEditor.java index 11159105a22..c1d7636e8f5 100755 --- a/org.eclipse.xtext.ui/src/org/eclipse/xtext/ui/editor/XtextEditor.java +++ b/org.eclipse.xtext.ui/src/org/eclipse/xtext/ui/editor/XtextEditor.java @@ -76,6 +76,8 @@ import org.eclipse.ui.PlatformUI; import org.eclipse.ui.editors.text.TextEditor; import org.eclipse.ui.texteditor.DefaultMarkerAnnotationAccess; +import org.eclipse.ui.texteditor.IDocumentProvider; +import org.eclipse.ui.texteditor.IDocumentProviderExtension3; import org.eclipse.ui.texteditor.ITextEditor; import org.eclipse.ui.texteditor.ITextEditorActionConstants; import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds; @@ -327,6 +329,16 @@ public void doSave(IProgressMonitor progressMonitor) { callback.afterSave(this); } + @Override + protected void performSave(boolean overwrite, IProgressMonitor progressMonitor) { + boolean unsyncOverwrite = false; + if (getDocumentProvider() instanceof IDocumentProviderExtension3) { + unsyncOverwrite = !((IDocumentProviderExtension3)getDocumentProvider()).isSynchronized(getEditorInput()); + } + super.performSave(overwrite || unsyncOverwrite, progressMonitor); + } + + @Override protected void updateState(final IEditorInput input) { new DisplayRunnable() { diff --git a/org.eclipse.xtext.ui/src/org/eclipse/xtext/ui/editor/model/XtextDocumentProvider.java b/org.eclipse.xtext.ui/src/org/eclipse/xtext/ui/editor/model/XtextDocumentProvider.java index c8a804bf3ca..6e6dd1cbd55 100644 --- a/org.eclipse.xtext.ui/src/org/eclipse/xtext/ui/editor/model/XtextDocumentProvider.java +++ b/org.eclipse.xtext.ui/src/org/eclipse/xtext/ui/editor/model/XtextDocumentProvider.java @@ -27,6 +27,7 @@ import org.eclipse.core.filesystem.IFileInfo; import org.eclipse.core.filesystem.IFileStore; import org.eclipse.core.resources.IEncodedStorage; +import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceStatus; import org.eclipse.core.resources.IStorage; import org.eclipse.core.runtime.Adapters; @@ -410,6 +411,14 @@ public long getModificationStamp(Object element) { @Override public boolean isSynchronized(Object element) { + if (!(element instanceof IFileEditorInput)) { + return super.isSynchronized(element); + } + IFileEditorInput input = (IFileEditorInput) element; + if (input.getFile() == null) { + return super.isSynchronized(element); + } + ElementInfo info = getElementInfo(element); long synchronizationStamp; if (info instanceof FileInfo) { @@ -419,7 +428,15 @@ public boolean isSynchronized(Object element) { } else { return super.isSynchronized(element); } - return synchronizationStamp == getModificationStamp(element); + + // Check has to fit the following method: + // org.eclipse.ui.editors.text.FileDocumentProvider.checkSynchronizationState(long, IResource) + boolean isSynchronized = synchronizationStamp == getSynchronizationStamp(input.getFile()); + if (!isSynchronized) { + // checks for is sync and triggers sync if necessary + input.getFile().isSynchronized(IResource.DEPTH_ZERO); + } + return isSynchronized; } @Override