IDEA-75303

This commit is contained in:
Alexey Kudravtsev
2011-10-12 18:32:21 +04:00
parent 3e838154af
commit edd94b80aa
8 changed files with 154 additions and 75 deletions
@@ -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;
@@ -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;
@@ -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<Boolean> canClose = new Ref<Boolean>(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" +
@@ -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)) {
@@ -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<Document> myUnsavedDocuments = new ConcurrentHashSet<Document>();
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<Document> failedToSave = new THashSet<Document>();
@@ -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();
@@ -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();
@@ -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<caret> \nyyy ";
@NonNls String text = "xxx<caret> \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<caret> \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<caret>");
}
private void doTest(String before, String after) throws IOException {
public void testModifyLineAndExitApplication_ShouldStripEvenWhenCaretIsAtTheChangedLine() throws IOException {
configureFromFileText("x.txt", "xxx <caret>\n");
type(' ');
ApplicationImpl application = (ApplicationImpl)ApplicationManager.getApplication();
application.setDisposeInProgress(true);
try {
FileDocumentManager.getInstance().saveAllDocuments();
checkResultByText("xxx<caret>\n");
}
finally {
application.setDisposeInProgress(false);
}
}
public void testModifyLine_Save_MoveCaret_SaveAgain_ShouldStrip() throws IOException {
configureFromFileText("x.txt", "xxx <caret>\nyyy\n");
type(' ');
FileDocumentManager.getInstance().saveAllDocuments();
checkResultByText("xxx <caret>\nyyy\n"); // caret in the way
myEditor.getCaretModel().moveToOffset(myEditor.getDocument().getText().indexOf("yyy"));
FileDocumentManager.getInstance().saveAllDocuments();
checkResultByText("xxx\n<caret>yyy\n"); // now we can strip
}
private void doTest(@NonNls String before, @NonNls String after) throws IOException {
configureFromFileText("x.txt", before);
type(' ');
backspace();
@@ -696,7 +696,6 @@
serviceImplementation="com.intellij.openapi.vcs.impl.CodeSmellDetectorImpl"/>
<editorTabTitleProvider implementation="com.intellij.openapi.fileEditor.impl.UniqueNameEditorTabTitleProvider"/>
<fileDocumentManagerListener implementation="com.intellij.openapi.fileEditor.impl.TrailingSpacesStripper"/>
<applicationService serviceImplementation="com.intellij.execution.JavaRunConfigurationExtensionManager"/>
<projectService serviceInterface="com.intellij.psi.util.PsiModificationTracker"