From edd94b80aa513b0641b9b6f5f3e1ae73a0c3d9ab Mon Sep 17 00:00:00 2001 From: Alexey Kudravtsev Date: Wed, 12 Oct 2011 15:25:48 +0400 Subject: [PATCH] IDEA-75303 --- .../openapi/editor/impl/DocumentImpl.java | 13 +- .../intellij/openapi/editor/impl/LineSet.java | 3 + .../application/impl/ApplicationImpl.java | 26 ++-- .../command/impl/DocumentUndoProvider.java | 1 + .../impl/FileDocumentManagerImpl.java | 112 +++++++++--------- .../impl/TrailingSpacesStripper.java | 37 +++++- .../editor/StripTrailingSpacesTest.java | 36 +++++- .../src/META-INF/LangExtensions.xml | 1 - 8 files changed, 154 insertions(+), 75 deletions(-) diff --git a/platform/core-impl/src/com/intellij/openapi/editor/impl/DocumentImpl.java b/platform/core-impl/src/com/intellij/openapi/editor/impl/DocumentImpl.java index 1686ef204f5f..4f35384097d5 100644 --- a/platform/core-impl/src/com/intellij/openapi/editor/impl/DocumentImpl.java +++ b/platform/core-impl/src/com/intellij/openapi/editor/impl/DocumentImpl.java @@ -161,8 +161,7 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx { } else { final int finalStart = whiteSpaceStart; - ApplicationManager - .getApplication().runWriteAction(new DocumentRunnable(this, project) { + ApplicationManager.getApplication().runWriteAction(new DocumentRunnable(this, project) { public void run() { CommandProcessor.getInstance().runUndoTransparentAction(new Runnable() { public void run() { @@ -345,7 +344,7 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx { throwGuardedFragment(marker, startOffset, sToDelete.toString(), null); } - myText.remove(this, startOffset, endOffset,sToDelete); + myText.remove(this, startOffset, endOffset, sToDelete); } public void replaceString(int startOffset, int endOffset, @NotNull CharSequence s) { @@ -475,6 +474,14 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx { myLineSet.clearModificationFlags(); } + public void clearLineModificationFlagsExcept(int caretLine) { + boolean wasModified = caretLine != -1 && myLineSet.isModified(caretLine); + clearLineModificationFlags(); + if (wasModified) { + myLineSet.setModified(caretLine); + } + } + @NotNull private DocumentEvent beforeChangedUpdate(int offset, CharSequence oldString, CharSequence newString, boolean wholeTextReplaced) { myChangeInProgress = true; diff --git a/platform/core-impl/src/com/intellij/openapi/editor/impl/LineSet.java b/platform/core-impl/src/com/intellij/openapi/editor/impl/LineSet.java index 9dc4468cc906..03a901e606db 100644 --- a/platform/core-impl/src/com/intellij/openapi/editor/impl/LineSet.java +++ b/platform/core-impl/src/com/intellij/openapi/editor/impl/LineSet.java @@ -53,6 +53,9 @@ public class LineSet{ final boolean isModified(int index) { return (mySegments.getSegmentData(index) & MODIFIED_MASK) != 0; } + final void setModified(int index) { + setSegmentModified(mySegments, index); + } final int getSeparatorLength(int index) { return mySegments.getSegmentData(index) & SEPARATOR_MASK; diff --git a/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java b/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java index e44b22fd8112..5eeb81bbae05 100644 --- a/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java @@ -67,6 +67,7 @@ import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; import org.picocontainer.MutablePicoContainer; import javax.swing.*; @@ -299,6 +300,7 @@ public class ApplicationImpl extends ComponentManagerImpl implements Application public void run() { if (ApplicationManager.getApplication() != ApplicationImpl.this) return; try { + myDisposeInProgress = true; saveAll(); } finally { @@ -311,7 +313,6 @@ public class ApplicationImpl extends ComponentManagerImpl implements Application } private boolean disposeSelf() { - myDisposeInProgress = true; final CommandProcessor commandProcessor = CommandProcessor.getInstance(); final Ref canClose = new Ref(Boolean.TRUE); for (final Project project : ProjectManagerEx.getInstanceEx().getOpenProjects()) { @@ -363,7 +364,7 @@ public class ApplicationImpl extends ComponentManagerImpl implements Application } return; // do not call super } - else if (fatal) { + if (fatal) { LOG.error(ex); @NonNls final String errorMessage = "Fatal error initializing class " + componentClassName + ":\n" + ex.toString() + @@ -747,15 +748,14 @@ public class ApplicationImpl extends ComponentManagerImpl implements Application Runnable runnable = new Runnable() { public void run() { - if (!force) { - if (!showConfirmation()) { - saveAll(); - myExitCode = 0; - return; - } + if (!force && !showConfirmation()) { + saveAll(); + myExitCode = 0; + return; } getMessageBus().syncPublisher(AppLifecycleListener.TOPIC).appClosing(); + myDisposeInProgress = true; FileDocumentManager.getInstance().saveAllDocuments(); @@ -910,7 +910,7 @@ public class ApplicationImpl extends ComponentManagerImpl implements Application for (int i = myWriteActionsStack.size() - 1; i >= 0; i--) { Class action = myWriteActionsStack.get(i); - if (actionClass == action || (action != null && ReflectionCache.isAssignable(actionClass, action))) return true; + if (actionClass == action || action != null && ReflectionCache.isAssignable(actionClass, action)) return true; } return false; } @@ -1056,7 +1056,7 @@ public class ApplicationImpl extends ComponentManagerImpl implements Application final IdeFrame ideFrame = (IdeFrame)frame; if (isActive() != active) { myActive = Boolean.valueOf(active); - System.setProperty("idea.active", Boolean.valueOf(myActive).toString()); + System.setProperty("idea.active", myActive.toString()); ApplicationActivationListener publisher = getMessageBus().syncPublisher(ApplicationActivationListener.TOPIC); if (active) { publisher.applicationActivated(ideFrame); @@ -1340,6 +1340,12 @@ public class ApplicationImpl extends ComponentManagerImpl implements Application return super.logSlowComponents() || ApplicationInfoImpl.getShadowInstance().isEAP(); } + @TestOnly + public void setDisposeInProgress(boolean disposeInProgress) { + myDisposeInProgress = disposeInProgress; + } + + @NonNls @Override public String toString() { return "Application" + diff --git a/platform/platform-impl/src/com/intellij/openapi/command/impl/DocumentUndoProvider.java b/platform/platform-impl/src/com/intellij/openapi/command/impl/DocumentUndoProvider.java index fc3f3a4d3e69..0f74e57cce7f 100644 --- a/platform/platform-impl/src/com/intellij/openapi/command/impl/DocumentUndoProvider.java +++ b/platform/platform-impl/src/com/intellij/openapi/command/impl/DocumentUndoProvider.java @@ -60,6 +60,7 @@ class DocumentUndoProvider implements Disposable { if (allEditorsAreViewersFor(document)) return; if (!shouldRecordActions(document)) return; + if (ApplicationManager.getApplication().isDisposeInProgress()) return; UndoManagerImpl undoManager = getUndoManager(); if (!undoManager.isActive() || !isUndoable(document)) { diff --git a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/FileDocumentManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/FileDocumentManagerImpl.java index d1ded8f05e43..940eb637f001 100644 --- a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/FileDocumentManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/FileDocumentManagerImpl.java @@ -35,7 +35,9 @@ import com.intellij.openapi.editor.event.DocumentEvent; import com.intellij.openapi.editor.ex.DocumentEx; import com.intellij.openapi.editor.ex.EditorSettingsExternalizable; import com.intellij.openapi.extensions.Extensions; -import com.intellij.openapi.fileEditor.*; +import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.fileEditor.FileDocumentManagerListener; +import com.intellij.openapi.fileEditor.FileDocumentSynchronizationVetoer; import com.intellij.openapi.fileTypes.BinaryFileTypeDecompilers; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.project.Project; @@ -67,7 +69,13 @@ import java.io.Writer; import java.lang.ref.Reference; import java.lang.ref.SoftReference; import java.lang.ref.WeakReference; -import java.util.*; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; public class FileDocumentManagerImpl extends FileDocumentManager implements ApplicationComponent, VirtualFileListener, SafeWriteRequestor { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.fileEditor.impl.FileDocumentManagerImpl"); @@ -78,17 +86,51 @@ public class FileDocumentManagerImpl extends FileDocumentManager implements Appl private final Set myUnsavedDocuments = new ConcurrentHashSet(); - private final VirtualFileManager myVirtualFileManager; private final MessageBus myBus; private static final Object lock = new Object(); + private final FileDocumentManagerListener myMultiCaster; + private final TrailingSpacesStripper myTrailingSpacesStripper = new TrailingSpacesStripper(); public FileDocumentManagerImpl(VirtualFileManager virtualFileManager) { - myVirtualFileManager = virtualFileManager; - - myVirtualFileManager.addVirtualFileListener(this); + virtualFileManager.addVirtualFileListener(this); myBus = ApplicationManager.getApplication().getMessageBus(); + InvocationHandler handler = new InvocationHandler() { + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + multicast(method, args); + return null; + } + }; + myMultiCaster = + (FileDocumentManagerListener)Proxy.newProxyInstance(FileDocumentManagerListener.class.getClassLoader(), new Class[]{FileDocumentManagerListener.class}, handler); + } + + private void multicast(Method method, Object[] args) { + try { + method.invoke(myBus.syncPublisher(AppTopics.FILE_DOCUMENT_SYNC), args); + } + catch (Exception e) { + LOG.error(e); + } + + // Allows pre-save document modification + for (FileDocumentManagerListener listener : getListeners()) { + try { + method.invoke(listener, args); + } + catch (Exception e) { + LOG.error(e); + } + } + + // stripping trailing spaces + try { + method.invoke(myTrailingSpacesStripper, args); + } + catch (Exception e) { + LOG.error(e); + } } @NotNull @@ -141,7 +183,7 @@ public class FileDocumentManagerImpl extends FileDocumentManager implements Appl } } - fireFileContentLoaded(file, document); + myMultiCaster.fileContentLoaded(file, document); } return document; @@ -218,11 +260,7 @@ public class FileDocumentManagerImpl extends FileDocumentManager implements Appl public void saveAllDocuments() { ApplicationManager.getApplication().assertIsDispatchThread(); - myBus.syncPublisher(AppTopics.FILE_DOCUMENT_SYNC).beforeAllDocumentsSaving(); - for (FileDocumentManagerListener listener : getListeners()) { - listener.beforeAllDocumentsSaving(); - } - + myMultiCaster.beforeAllDocumentsSaving(); if (myUnsavedDocuments.isEmpty()) return; Set failedToSave = new THashSet(); @@ -295,17 +333,7 @@ public class FileDocumentManagerImpl extends FileDocumentManager implements Appl } } - try { - myBus.syncPublisher(AppTopics.FILE_DOCUMENT_SYNC).beforeDocumentSaving(document); - } - catch (Exception e) { - LOG.error(e); - } - - // Allows pre-save document modification, e.g. stripping trailing spaces. - for (FileDocumentManagerListener listener : getListeners()) { - listener.beforeDocumentSaving(document); - } + myMultiCaster.beforeDocumentSaving(document); LOG.assertTrue(file.isValid()); @@ -336,7 +364,7 @@ public class FileDocumentManagerImpl extends FileDocumentManager implements Appl if (committed) { myUnsavedDocuments.remove(document); LOG.assertTrue(!myUnsavedDocuments.contains(document)); - ((DocumentEx)document).clearLineModificationFlags(); + myTrailingSpacesStripper.clearLineModificationFlags(document); } } } @@ -475,10 +503,7 @@ public class FileDocumentManagerImpl extends FileDocumentManager implements Appl } private void fireFileWithNoDocumentChanged(final VirtualFile file) { - myBus.syncPublisher(AppTopics.FILE_DOCUMENT_SYNC).fileWithNoDocumentChanged(file); - for (FileDocumentManagerListener listener : getListeners()) { - listener.fileWithNoDocumentChanged(file); - } + myMultiCaster.fileWithNoDocumentChanged(file); } public void reloadFromDisk(@NotNull final Document document) { @@ -507,12 +532,7 @@ public class FileDocumentManagerImpl extends FileDocumentManager implements Appl myUnsavedDocuments.remove(document); - try { - fireFileContentReloaded(file, document); - } - catch (Exception e) { - LOG.error(e); - } + myMultiCaster.fileContentReloaded(file, document); } protected boolean askReloadFromDisk(final VirtualFile file, final Document document) { @@ -595,18 +615,8 @@ public class FileDocumentManagerImpl extends FileDocumentManager implements Appl public void beforeFileMovement(VirtualFileMoveEvent event) { } - private void fireFileContentReloaded(final VirtualFile file, final Document document) { - myBus.syncPublisher(AppTopics.FILE_DOCUMENT_SYNC).fileContentReloaded(file, document); - for (FileDocumentManagerListener listener : getListeners()) { - listener.fileContentReloaded(file, document); - } - } - private void fireUnsavedDocumentsDropped() { - myBus.syncPublisher(AppTopics.FILE_DOCUMENT_SYNC).unsavedDocumentsDropped(); - for (FileDocumentManagerListener listener : getListeners()) { - listener.unsavedDocumentsDropped(); - } + myMultiCaster.unsavedDocumentsDropped(); } private boolean fireBeforeFileContentReload(final VirtualFile file, final Document document) { @@ -621,20 +631,10 @@ public class FileDocumentManagerImpl extends FileDocumentManager implements Appl } } - myBus.syncPublisher(AppTopics.FILE_DOCUMENT_SYNC).beforeFileContentReload(file, document); - for (FileDocumentManagerListener listener : getListeners()) { - listener.beforeFileContentReload(file, document); - } + myMultiCaster.beforeFileContentReload(file, document); return true; } - private void fireFileContentLoaded(final VirtualFile file, final DocumentEx document) { - myBus.syncPublisher(AppTopics.FILE_DOCUMENT_SYNC).fileContentLoaded(file, document); - for (FileDocumentManagerListener listener : getListeners()) { - listener.fileContentLoaded(file, document); - } - } - @NotNull protected FileDocumentManagerListener[] getListeners() { return FileDocumentManagerListener.EP_NAME.getExtensions(); diff --git a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/TrailingSpacesStripper.java b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/TrailingSpacesStripper.java index c56a02ee7b84..6348621bf98d 100644 --- a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/TrailingSpacesStripper.java +++ b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/TrailingSpacesStripper.java @@ -33,6 +33,7 @@ import com.intellij.openapi.util.ShutDownTracker; import com.intellij.openapi.wm.IdeFocusManager; import com.intellij.util.text.CharArrayUtil; import gnu.trove.THashSet; +import org.jetbrains.annotations.NotNull; import java.util.Set; @@ -65,7 +66,7 @@ public final class TrailingSpacesStripper extends FileDocumentManagerAdapter { if (doStrip) { final boolean inChangedLinesOnly = !stripTrailingSpaces.equals(EditorSettingsExternalizable.STRIP_TRAILING_SPACES_WHOLE); - boolean success = stripIfNotCurrentLine((DocumentImpl)document, inChangedLinesOnly); + boolean success = stripIfNotCurrentLine(document, inChangedLinesOnly); if (!success) { myDocumentsToStripLater.add(document); } @@ -95,6 +96,37 @@ public final class TrailingSpacesStripper extends FileDocumentManagerAdapter { } } + // clears line modification flags except lines which was not stripped because the caret was in the way + public void clearLineModificationFlags(@NotNull Document document) { + if (document instanceof DocumentWindow) { + document = ((DocumentWindow) document).getDelegate(); + } + if (!(document instanceof DocumentImpl)) { + return; + } + if (!myDocumentsToStripLater.contains(document)) return; + + DataContext dataContext = DataManager.getInstance().getDataContext(IdeFocusManager.getGlobalInstance().getFocusOwner()); + boolean isDisposeInProgress = ApplicationManager.getApplication().isDisposeInProgress(); // ignore caret placing when exiting + Editor activeEditor = isDisposeInProgress ? null : PlatformDataKeys.EDITOR.getData(dataContext); + + // when virtual space enabled, we can strip whitespace anywhere + boolean isVirtualSpaceEnabled = activeEditor == null || activeEditor.getSettings().isVirtualSpace(); + + int caretLine = activeEditor == null ? -1 : activeEditor.getCaretModel().getLogicalPosition().line; + + final EditorSettingsExternalizable settings = EditorSettingsExternalizable.getInstance(); + if (settings == null) return; + + String stripTrailingSpaces = settings.getStripTrailingSpaces(); + final boolean doStrip = !stripTrailingSpaces.equals(EditorSettingsExternalizable.STRIP_TRAILING_SPACES_NONE); + + final boolean inChangedLinesOnly = !stripTrailingSpaces.equals(EditorSettingsExternalizable.STRIP_TRAILING_SPACES_WHOLE); + if (!inChangedLinesOnly || !doStrip || isVirtualSpaceEnabled) caretLine = -1; + + ((DocumentImpl)document).clearLineModificationFlagsExcept(caretLine); + } + public static boolean stripIfNotCurrentLine(Document document, boolean inChangedLinesOnly) { if (document instanceof DocumentWindow) { document = ((DocumentWindow) document).getDelegate(); @@ -103,7 +135,8 @@ public final class TrailingSpacesStripper extends FileDocumentManagerAdapter { return true; } DataContext dataContext = DataManager.getInstance().getDataContext(IdeFocusManager.getGlobalInstance().getFocusOwner()); - Editor activeEditor = PlatformDataKeys.EDITOR.getData(dataContext); + boolean isDisposeInProgress = ApplicationManager.getApplication().isDisposeInProgress(); // ignore caret placing when exiting + Editor activeEditor = isDisposeInProgress ? null : PlatformDataKeys.EDITOR.getData(dataContext); // when virtual space enabled, we can strip whitespace anywhere boolean isVirtualSpaceEnabled = activeEditor == null || activeEditor.getSettings().isVirtualSpace(); diff --git a/platform/platform-impl/testSrc/com/intellij/openapi/editor/StripTrailingSpacesTest.java b/platform/platform-impl/testSrc/com/intellij/openapi/editor/StripTrailingSpacesTest.java index aba277b13774..509b06ec8741 100644 --- a/platform/platform-impl/testSrc/com/intellij/openapi/editor/StripTrailingSpacesTest.java +++ b/platform/platform-impl/testSrc/com/intellij/openapi/editor/StripTrailingSpacesTest.java @@ -1,11 +1,14 @@ package com.intellij.openapi.editor; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.impl.ApplicationImpl; import com.intellij.openapi.editor.ex.DocumentEx; import com.intellij.openapi.editor.ex.EditorSettingsExternalizable; +import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileEditor.impl.TrailingSpacesStripper; import com.intellij.testFramework.LightPlatformCodeInsightTestCase; import org.jdom.Element; +import org.jetbrains.annotations.NonNls; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; @@ -57,7 +60,7 @@ public class StripTrailingSpacesTest extends LightPlatformCodeInsightTestCase { } public void testOnlyModifiedLinesGetStripped() throws IOException { - String text = "xxx \nyyy "; + @NonNls String text = "xxx \nyyy "; configureFromFileText("x.txt", text); ((DocumentEx)myEditor.getDocument()).clearLineModificationFlags(); stripTrailingSpaces(); @@ -71,14 +74,41 @@ public class StripTrailingSpacesTest extends LightPlatformCodeInsightTestCase { public void testOnlyModifiedLinesWhenDoesNotAllowCaretAfterEndOfLine() throws IOException { configureFromFileText("x.txt", "xxx \nZ "); type(' '); - myEditor.getCaretModel().moveToOffset(myEditor.getDocument().getText().indexOf("Z")+1); + myEditor.getCaretModel().moveToOffset(myEditor.getDocument().getText().indexOf("Z") + 1); type('Z'); stripTrailingSpaces(); checkResultByText("xxx\nZZ"); } - private void doTest(String before, String after) throws IOException { + public void testModifyLineAndExitApplication_ShouldStripEvenWhenCaretIsAtTheChangedLine() throws IOException { + configureFromFileText("x.txt", "xxx \n"); + type(' '); + + ApplicationImpl application = (ApplicationImpl)ApplicationManager.getApplication(); + application.setDisposeInProgress(true); + + try { + FileDocumentManager.getInstance().saveAllDocuments(); + checkResultByText("xxx\n"); + } + finally { + application.setDisposeInProgress(false); + } + } + + public void testModifyLine_Save_MoveCaret_SaveAgain_ShouldStrip() throws IOException { + configureFromFileText("x.txt", "xxx \nyyy\n"); + type(' '); + FileDocumentManager.getInstance().saveAllDocuments(); + checkResultByText("xxx \nyyy\n"); // caret in the way + myEditor.getCaretModel().moveToOffset(myEditor.getDocument().getText().indexOf("yyy")); + + FileDocumentManager.getInstance().saveAllDocuments(); + checkResultByText("xxx\nyyy\n"); // now we can strip + } + + private void doTest(@NonNls String before, @NonNls String after) throws IOException { configureFromFileText("x.txt", before); type(' '); backspace(); diff --git a/platform/platform-resources/src/META-INF/LangExtensions.xml b/platform/platform-resources/src/META-INF/LangExtensions.xml index 0b369c9c99af..11aabf96956a 100644 --- a/platform/platform-resources/src/META-INF/LangExtensions.xml +++ b/platform/platform-resources/src/META-INF/LangExtensions.xml @@ -696,7 +696,6 @@ serviceImplementation="com.intellij.openapi.vcs.impl.CodeSmellDetectorImpl"/> -