blob: ec2559fc06ba337eb2a78cb555690aaa962a4510 [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.openapi.fileEditor.impl;
import com.intellij.AppTopics;
import com.intellij.CommonBundle;
import com.intellij.codeStyle.CodeStyleFacade;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.AccessToken;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.command.UndoConfirmationPolicy;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.diff.DiffManager;
import com.intellij.openapi.diff.DocumentContent;
import com.intellij.openapi.diff.SimpleContent;
import com.intellij.openapi.diff.SimpleDiffRequest;
import com.intellij.openapi.diff.ex.DiffPanelOptions;
import com.intellij.openapi.diff.impl.DiffPanelImpl;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.editor.event.DocumentAdapter;
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.editor.impl.EditorFactoryImpl;
import com.intellij.openapi.editor.impl.TrailingSpacesStripper;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.fileEditor.*;
import com.intellij.openapi.fileEditor.impl.text.TextEditorImpl;
import com.intellij.openapi.fileTypes.BinaryFileTypeDecompilers;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.fileTypes.UnknownFileType;
import com.intellij.openapi.project.*;
import com.intellij.openapi.project.ex.ProjectEx;
import com.intellij.openapi.ui.DialogBuilder;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.io.FileUtilRt;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.*;
import com.intellij.openapi.vfs.ex.dummy.DummyFileSystem;
import com.intellij.openapi.vfs.newvfs.NewVirtualFileSystem;
import com.intellij.pom.core.impl.PomModelImpl;
import com.intellij.psi.ExternalChangeAction;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.SingleRootFileViewProvider;
import com.intellij.testFramework.LightVirtualFile;
import com.intellij.ui.UIBundle;
import com.intellij.ui.components.JBScrollPane;
import com.intellij.util.Function;
import com.intellij.util.PairProcessor;
import com.intellij.util.ThrowableRunnable;
import com.intellij.util.containers.ConcurrentHashSet;
import com.intellij.util.containers.ConcurrentWeakValueHashMap;
import com.intellij.util.messages.MessageBus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.*;
import java.util.List;
public class FileDocumentManagerImpl extends FileDocumentManager implements VirtualFileListener,
ProjectManagerListener, SafeWriteRequestor {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.fileEditor.impl.FileDocumentManagerImpl");
private static final Key<String> LINE_SEPARATOR_KEY = Key.create("LINE_SEPARATOR_KEY");
public static final Key<Document> HARD_REF_TO_DOCUMENT_KEY = Key.create("HARD_REF_TO_DOCUMENT_KEY");
private static final Key<VirtualFile> FILE_KEY = Key.create("FILE_KEY");
private static final Key<Boolean> MUST_RECOMPUTE_FILE_TYPE = Key.create("Must recompute file type");
private final Set<Document> myUnsavedDocuments = new ConcurrentHashSet<Document>();
private final Map<VirtualFile, Document> myDocuments = new ConcurrentWeakValueHashMap<VirtualFile, Document>();
private final MessageBus myBus;
private static final Object lock = new Object();
private final FileDocumentManagerListener myMultiCaster;
private final TrailingSpacesStripper myTrailingSpacesStripper = new TrailingSpacesStripper();
private boolean myOnClose = false;
public FileDocumentManagerImpl(@NotNull VirtualFileManager virtualFileManager, @NotNull ProjectManager projectManager) {
virtualFileManager.addVirtualFileListener(this);
projectManager.addProjectManagerListener(this);
myBus = ApplicationManager.getApplication().getMessageBus();
InvocationHandler handler = new InvocationHandler() {
@Nullable
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
multiCast(method, args);
return null;
}
};
final ClassLoader loader = FileDocumentManagerListener.class.getClassLoader();
myMultiCaster = (FileDocumentManagerListener)Proxy.newProxyInstance(loader, new Class[]{FileDocumentManagerListener.class}, handler);
}
@SuppressWarnings("OverlyBroadCatchBlock")
private void multiCast(@NotNull Method method, Object[] args) {
try {
method.invoke(myBus.syncPublisher(AppTopics.FILE_DOCUMENT_SYNC), args);
}
catch (ClassCastException e) {
LOG.error("Arguments: "+ Arrays.toString(args), e);
}
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);
}
}
@Override
@Nullable
public Document getDocument(@NotNull final VirtualFile file) {
DocumentEx document = (DocumentEx)getCachedDocument(file);
if (document == null) {
if (file.isDirectory() || SingleRootFileViewProvider.isTooLargeForContentLoading(file)) {
return null;
}
if (isBinaryWithoutDecompiler(file)) {
return null;
}
final CharSequence text = LoadTextUtil.loadText(file);
synchronized (lock) {
document = (DocumentEx)getCachedDocument(file);
if (document != null) return document; // Double checking
document = (DocumentEx)createDocument(text, file);
document.setModificationStamp(file.getModificationStamp());
final FileType fileType = file.getFileType();
document.setReadOnly(!file.isWritable() || fileType.isBinary());
if (file instanceof LightVirtualFile) {
registerDocument(document, file);
} else {
myDocuments.put(file, document);
document.putUserData(FILE_KEY, file);
}
if (!(file instanceof LightVirtualFile || file.getFileSystem() instanceof DummyFileSystem)) {
document.addDocumentListener(
new DocumentAdapter() {
@Override
public void documentChanged(DocumentEvent e) {
final Document document = e.getDocument();
myUnsavedDocuments.add(document);
final Runnable currentCommand = CommandProcessor.getInstance().getCurrentCommand();
Project project = currentCommand == null ? null : CommandProcessor.getInstance().getCurrentCommandProject();
String lineSeparator = CodeStyleFacade.getInstance(project).getLineSeparator();
document.putUserData(LINE_SEPARATOR_KEY, lineSeparator);
// avoid documents piling up during batch processing
if (areTooManyDocumentsInTheQueue(myUnsavedDocuments)) {
saveAllDocumentsLater();
}
}
}
);
}
}
myMultiCaster.fileContentLoaded(file, document);
}
return document;
}
public static boolean areTooManyDocumentsInTheQueue(Collection<Document> documents) {
if (documents.size() > 100) return true;
int totalSize = 0;
for (Document document : documents) {
totalSize += document.getTextLength();
if (totalSize > 10 * FileUtilRt.MEGABYTE) return true;
}
return false;
}
private static Document createDocument(final CharSequence text, VirtualFile file) {
boolean acceptSlashR = file instanceof LightVirtualFile && StringUtil.indexOf(text, '\r') >= 0;
return ((EditorFactoryImpl)EditorFactory.getInstance()).createDocument(text, acceptSlashR, false);
}
@Override
@Nullable
public Document getCachedDocument(@NotNull VirtualFile file) {
Document hard = file.getUserData(HARD_REF_TO_DOCUMENT_KEY);
return hard != null ? hard : myDocuments.get(file);
}
public static void registerDocument(@NotNull final Document document, @NotNull VirtualFile virtualFile) {
synchronized (lock) {
virtualFile.putUserData(HARD_REF_TO_DOCUMENT_KEY, document);
document.putUserData(FILE_KEY, virtualFile);
}
}
@Override
@Nullable
public VirtualFile getFile(@NotNull Document document) {
return document.getUserData(FILE_KEY);
}
@TestOnly
public void dropAllUnsavedDocuments() {
if (!ApplicationManager.getApplication().isUnitTestMode()) {
throw new RuntimeException("This method is only for test mode!");
}
ApplicationManager.getApplication().assertWriteAccessAllowed();
if (!myUnsavedDocuments.isEmpty()) {
myUnsavedDocuments.clear();
fireUnsavedDocumentsDropped();
}
}
private void saveAllDocumentsLater() {
// later because some document might have been blocked by PSI right now
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
if (ApplicationManager.getApplication().isDisposed()) {
return;
}
final Document[] unsavedDocuments = getUnsavedDocuments();
for (Document document : unsavedDocuments) {
VirtualFile file = getFile(document);
if (file == null) continue;
Project project = ProjectUtil.guessProjectForFile(file);
if (project == null) continue;
if (PsiDocumentManager.getInstance(project).isDocumentBlockedByPsi(document)) continue;
saveDocument(document);
}
}
});
}
@Override
public void saveAllDocuments() {
saveAllDocuments(true);
}
/**
* @param isExplicit caused by user directly (Save action) or indirectly (e.g. Compile)
*/
public void saveAllDocuments(boolean isExplicit) {
ApplicationManager.getApplication().assertIsDispatchThread();
myMultiCaster.beforeAllDocumentsSaving();
if (myUnsavedDocuments.isEmpty()) return;
final Map<Document, IOException> failedToSave = new HashMap<Document, IOException>();
final Set<Document> vetoed = new HashSet<Document>();
while (true) {
int count = 0;
for (Document document : myUnsavedDocuments) {
if (failedToSave.containsKey(document)) continue;
if (vetoed.contains(document)) continue;
try {
doSaveDocument(document, isExplicit);
}
catch (IOException e) {
//noinspection ThrowableResultOfMethodCallIgnored
failedToSave.put(document, e);
}
catch (SaveVetoException e) {
vetoed.add(document);
}
count++;
}
if (count == 0) break;
}
if (!failedToSave.isEmpty()) {
handleErrorsOnSave(failedToSave);
}
}
@Override
public void saveDocument(@NotNull final Document document) {
saveDocument(document, true);
}
public void saveDocument(@NotNull final Document document, final boolean explicit) {
ApplicationManager.getApplication().assertIsDispatchThread();
if (!myUnsavedDocuments.contains(document)) return;
try {
doSaveDocument(document, explicit);
}
catch (IOException e) {
handleErrorsOnSave(Collections.singletonMap(document, e));
}
catch (SaveVetoException ignored) {
}
}
@Override
public void saveDocumentAsIs(@NotNull Document document) {
EditorSettingsExternalizable editorSettings = EditorSettingsExternalizable.getInstance();
String trailer = editorSettings.getStripTrailingSpaces();
boolean ensureEOLonEOF = editorSettings.isEnsureNewLineAtEOF();
editorSettings.setStripTrailingSpaces(EditorSettingsExternalizable.STRIP_TRAILING_SPACES_NONE);
editorSettings.setEnsureNewLineAtEOF(false);
try {
saveDocument(document);
}
finally {
editorSettings.setStripTrailingSpaces(trailer);
editorSettings.setEnsureNewLineAtEOF(ensureEOLonEOF);
}
}
private static class SaveVetoException extends Exception {}
private void doSaveDocument(@NotNull final Document document, boolean isExplicit) throws IOException, SaveVetoException {
VirtualFile file = getFile(document);
if (file == null || file instanceof LightVirtualFile || file.isValid() && !isFileModified(file)) {
removeFromUnsaved(document);
return;
}
if (file.isValid() && needsRefresh(file)) {
file.refresh(false, false);
if (!myUnsavedDocuments.contains(document)) return;
}
for (FileDocumentSynchronizationVetoer vetoer : Extensions.getExtensions(FileDocumentSynchronizationVetoer.EP_NAME)) {
if (!vetoer.maySaveDocument(document, isExplicit)) {
throw new SaveVetoException();
}
}
final AccessToken token = ApplicationManager.getApplication().acquireWriteActionLock(getClass());
try {
doSaveDocumentInWriteAction(document, file);
}
finally {
token.finish();
}
}
private void doSaveDocumentInWriteAction(@NotNull final Document document, @NotNull final VirtualFile file) throws IOException {
if (!file.isValid()) {
removeFromUnsaved(document);
return;
}
if (!file.equals(getFile(document))) {
registerDocument(document, file);
}
if (!isSaveNeeded(document, file)) {
if (document instanceof DocumentEx) {
((DocumentEx)document).setModificationStamp(file.getModificationStamp());
}
removeFromUnsaved(document);
updateModifiedProperty(file);
return;
}
PomModelImpl.guardPsiModificationsIn(new ThrowableRunnable<IOException>() {
@Override
public void run() throws IOException {
myMultiCaster.beforeDocumentSaving(document);
LOG.assertTrue(file.isValid());
String text = document.getText();
String lineSeparator = getLineSeparator(document, file);
if (!lineSeparator.equals("\n")) {
text = StringUtil.convertLineSeparators(text, lineSeparator);
}
Project project = ProjectLocator.getInstance().guessProjectForFile(file);
LoadTextUtil.write(project, file, FileDocumentManagerImpl.this, text, document.getModificationStamp());
myUnsavedDocuments.remove(document);
LOG.assertTrue(!myUnsavedDocuments.contains(document));
myTrailingSpacesStripper.clearLineModificationFlags(document);
}
});
}
private static void updateModifiedProperty(@NotNull VirtualFile file) {
for (Project project : ProjectManager.getInstance().getOpenProjects()) {
FileEditorManager fileEditorManager = FileEditorManager.getInstance(project);
for (FileEditor editor : fileEditorManager.getAllEditors(file)) {
if (editor instanceof TextEditorImpl) {
((TextEditorImpl)editor).updateModifiedProperty();
}
}
}
}
private void removeFromUnsaved(@NotNull Document document) {
myUnsavedDocuments.remove(document);
fireUnsavedDocumentsDropped();
LOG.assertTrue(!myUnsavedDocuments.contains(document));
}
private static boolean isSaveNeeded(@NotNull Document document, @NotNull VirtualFile file) throws IOException {
if (file.getFileType().isBinary() || document.getTextLength() > 1000 * 1000) { // don't compare if the file is too big
return true;
}
byte[] bytes = file.contentsToByteArray();
CharSequence loaded = LoadTextUtil.getTextByBinaryPresentation(bytes, file, false, false);
return !Comparing.equal(document.getCharsSequence(), loaded);
}
private static boolean needsRefresh(final VirtualFile file) {
final VirtualFileSystem fs = file.getFileSystem();
return fs instanceof NewVirtualFileSystem && file.getTimeStamp() != ((NewVirtualFileSystem)fs).getTimeStamp(file);
}
@NotNull
public static String getLineSeparator(@NotNull Document document, @NotNull VirtualFile file) {
String lineSeparator = LoadTextUtil.getDetectedLineSeparator(file);
if (lineSeparator == null) {
lineSeparator = document.getUserData(LINE_SEPARATOR_KEY);
assert lineSeparator != null : document;
}
return lineSeparator;
}
@Override
@NotNull
public String getLineSeparator(@Nullable VirtualFile file, @Nullable Project project) {
String lineSeparator = file == null ? null : LoadTextUtil.getDetectedLineSeparator(file);
if (lineSeparator == null) {
CodeStyleFacade settingsManager = project == null
? CodeStyleFacade.getInstance()
: CodeStyleFacade.getInstance(project);
lineSeparator = settingsManager.getLineSeparator();
}
return lineSeparator;
}
@Override
public boolean requestWriting(@NotNull Document document, Project project) {
final VirtualFile file = getInstance().getFile(document);
if (project != null && file != null && file.isValid()) {
return !file.getFileType().isBinary() && ReadonlyStatusHandler.ensureFilesWritable(project, file);
}
if (document.isWritable()) {
return true;
}
document.fireReadOnlyModificationAttempt();
return false;
}
@Override
public void reloadFiles(@NotNull final VirtualFile... files) {
for (VirtualFile file : files) {
if (file.exists()) {
final Document doc = getCachedDocument(file);
if (doc != null) {
reloadFromDisk(doc);
}
}
}
}
@Override
@NotNull
public Document[] getUnsavedDocuments() {
if (myUnsavedDocuments.isEmpty()) {
return Document.EMPTY_ARRAY;
}
List<Document> list = new ArrayList<Document>(myUnsavedDocuments);
return list.toArray(new Document[list.size()]);
}
@Override
public boolean isDocumentUnsaved(@NotNull Document document) {
return myUnsavedDocuments.contains(document);
}
@Override
public boolean isFileModified(@NotNull VirtualFile file) {
final Document doc = getCachedDocument(file);
return doc != null && isDocumentUnsaved(doc) && doc.getModificationStamp() != file.getModificationStamp();
}
@Override
public void propertyChanged(@NotNull VirtualFilePropertyEvent event) {
final VirtualFile file = event.getFile();
if (VirtualFile.PROP_WRITABLE.equals(event.getPropertyName())) {
final Document document = getCachedDocument(file);
if (document != null) {
ApplicationManager.getApplication().runWriteAction(new ExternalChangeAction() {
@Override
public void run() {
document.setReadOnly(!file.isWritable());
}
});
}
}
else if (VirtualFile.PROP_NAME.equals(event.getPropertyName())) {
Document document = getCachedDocument(file);
if (document != null) {
// a file is linked to a document - chances are it is an "unknown text file" now
if (isBinaryWithoutDecompiler(file)) {
unbindFileFromDocument(file, document);
}
}
}
}
private void unbindFileFromDocument(@NotNull VirtualFile file, @NotNull Document document) {
myDocuments.remove(file);
file.putUserData(HARD_REF_TO_DOCUMENT_KEY, null);
document.putUserData(FILE_KEY, null);
}
private static boolean isBinaryWithDecompiler(@NotNull VirtualFile file) {
final FileType ft = file.getFileType();
return ft.isBinary() && BinaryFileTypeDecompilers.INSTANCE.forFileType(ft) != null;
}
private static boolean isBinaryWithoutDecompiler(@NotNull VirtualFile file) {
final FileType fileType = file.getFileType();
return fileType.isBinary() && BinaryFileTypeDecompilers.INSTANCE.forFileType(fileType) == null;
}
@Override
public void contentsChanged(@NotNull VirtualFileEvent event) {
if (event.isFromSave()) return;
final VirtualFile file = event.getFile();
final Document document = getCachedDocument(file);
if (document == null) {
myMultiCaster.fileWithNoDocumentChanged(file);
return;
}
if (isBinaryWithDecompiler(file)) {
myMultiCaster.fileWithNoDocumentChanged(file); // This will generate PSI event at FileManagerImpl
}
long documentStamp = document.getModificationStamp();
long oldFileStamp = event.getOldModificationStamp();
if (documentStamp != oldFileStamp) {
LOG.info("reload " + file.getName() + " from disk?");
LOG.info(" documentStamp:" + documentStamp);
LOG.info(" oldFileStamp:" + oldFileStamp);
if (file.isValid() && askReloadFromDisk(file, document)) {
reloadFromDisk(document);
}
}
else {
reloadFromDisk(document);
}
}
@Override
public void reloadFromDisk(@NotNull final Document document) {
ApplicationManager.getApplication().assertIsDispatchThread();
final VirtualFile file = getFile(document);
assert file != null;
if (!fireBeforeFileContentReload(file, document)) {
return;
}
if (file.getLength() > FileUtilRt.LARGE_FOR_CONTENT_LOADING) {
unbindFileFromDocument(file, document);
myUnsavedDocuments.remove(document);
myMultiCaster.fileWithNoDocumentChanged(file);
return;
}
final Project project = ProjectLocator.getInstance().guessProjectForFile(file);
CommandProcessor.getInstance().executeCommand(project, new Runnable() {
@Override
public void run() {
ApplicationManager.getApplication().runWriteAction(
new ExternalChangeAction.ExternalDocumentChange(document, project) {
@Override
public void run() {
boolean wasWritable = document.isWritable();
DocumentEx documentEx = (DocumentEx)document;
documentEx.setReadOnly(false);
LoadTextUtil.setCharsetWasDetectedFromBytes(file, null);
file.setBOM(null); // reset BOM in case we had one and the external change stripped it away
documentEx.replaceText(LoadTextUtil.loadText(file), file.getModificationStamp());
documentEx.setReadOnly(!wasWritable);
}
}
);
}
}, UIBundle.message("file.cache.conflict.action"), null, UndoConfirmationPolicy.REQUEST_CONFIRMATION);
myUnsavedDocuments.remove(document);
myMultiCaster.fileContentReloaded(file, document);
}
private PairProcessor<VirtualFile, Document> askReloadFromDisk = new PairProcessor<VirtualFile, Document>() {
@Override
public boolean process(final VirtualFile file, final Document document) {
String message = UIBundle.message("file.cache.conflict.message.text", file.getPresentableUrl());
final DialogBuilder builder = new DialogBuilder();
builder.setCenterPanel(new JLabel(message, Messages.getQuestionIcon(), SwingConstants.CENTER));
builder.addOkAction().setText(UIBundle.message("file.cache.conflict.load.fs.changes.button"));
builder.addCancelAction().setText(UIBundle.message("file.cache.conflict.keep.memory.changes.button"));
builder.addAction(new AbstractAction(UIBundle.message("file.cache.conflict.show.difference.button")) {
@Override
public void actionPerformed(ActionEvent e) {
String title = UIBundle.message("file.cache.conflict.for.file.dialog.title", file.getPresentableUrl());
final ProjectEx project = (ProjectEx)ProjectLocator.getInstance().guessProjectForFile(file);
SimpleDiffRequest request = new SimpleDiffRequest(project, title);
FileType fileType = file.getFileType();
String fsContent = LoadTextUtil.loadText(file).toString();
request.setContents(new SimpleContent(fsContent, fileType),
new DocumentContent(project, document, fileType));
request.setContentTitles(UIBundle.message("file.cache.conflict.diff.content.file.system.content"),
UIBundle.message("file.cache.conflict.diff.content.memory.content"));
DialogBuilder diffBuilder = new DialogBuilder(project);
DiffPanelImpl diffPanel = (DiffPanelImpl)DiffManager.getInstance().createDiffPanel(diffBuilder.getWindow(), project, diffBuilder, null);
diffPanel.getOptions().setShowSourcePolicy(DiffPanelOptions.ShowSourcePolicy.DONT_SHOW);
diffBuilder.setCenterPanel(diffPanel.getComponent());
diffBuilder.setDimensionServiceKey("FileDocumentManager.FileCacheConflict");
diffPanel.setDiffRequest(request);
diffBuilder.addOkAction().setText(UIBundle.message("file.cache.conflict.save.changes.button"));
diffBuilder.addCancelAction();
diffBuilder.setTitle(title);
if (diffBuilder.show() == DialogWrapper.OK_EXIT_CODE) {
builder.getDialogWrapper().close(DialogWrapper.CANCEL_EXIT_CODE);
}
}
});
builder.setTitle(UIBundle.message("file.cache.conflict.dialog.title"));
builder.setButtonsAlignment(SwingConstants.CENTER);
builder.setHelpId("reference.dialogs.fileCacheConflict");
return builder.show() == 0;
}
};
@TestOnly
public void setAskReloadFromDisk(@NotNull Disposable disposable,
@NotNull PairProcessor<VirtualFile, Document> newProcessor) {
final PairProcessor<VirtualFile, Document> old = askReloadFromDisk;
askReloadFromDisk = newProcessor;
Disposer.register(disposable, new Disposable() {
@Override
public void dispose() {
askReloadFromDisk = old;
}
});
}
private boolean askReloadFromDisk(final VirtualFile file, final Document document) {
ApplicationManager.getApplication().assertIsDispatchThread();
if (!isDocumentUnsaved(document)) return true;
return askReloadFromDisk.process(file, document);
}
@Override
public void fileCreated(@NotNull VirtualFileEvent event) {
}
@Override
public void fileDeleted(@NotNull VirtualFileEvent event) {
Document doc = getCachedDocument(event.getFile());
if (doc != null) {
myTrailingSpacesStripper.documentDeleted(doc);
}
}
@Override
public void fileMoved(@NotNull VirtualFileMoveEvent event) {
}
@Override
public void fileCopied(@NotNull VirtualFileCopyEvent event) {
fileCreated(event);
}
@Override
public void beforePropertyChange(@NotNull VirtualFilePropertyEvent event) {
}
@Override
public void beforeContentsChange(@NotNull VirtualFileEvent event) {
VirtualFile virtualFile = event.getFile();
if (virtualFile.getFileType() == UnknownFileType.INSTANCE && virtualFile.getLength() == 0) {
virtualFile.putUserData(MUST_RECOMPUTE_FILE_TYPE, Boolean.TRUE);
}
}
public static boolean recomputeFileTypeIfNecessary(@NotNull VirtualFile virtualFile) {
if (virtualFile.getUserData(MUST_RECOMPUTE_FILE_TYPE) != null) {
virtualFile.getFileType();
virtualFile.putUserData(MUST_RECOMPUTE_FILE_TYPE, null);
return true;
}
return false;
}
@Override
public void beforeFileDeletion(@NotNull VirtualFileEvent event) {
}
@Override
public void beforeFileMovement(@NotNull VirtualFileMoveEvent event) {
}
@Override
public void projectOpened(Project project) {
}
@Override
public boolean canCloseProject(Project project) {
if (!myUnsavedDocuments.isEmpty()) {
myOnClose = true;
try {
saveAllDocuments();
}
finally {
myOnClose = false;
}
}
return myUnsavedDocuments.isEmpty();
}
@Override
public void projectClosed(Project project) {
}
@Override
public void projectClosing(Project project) {
}
private void fireUnsavedDocumentsDropped() {
myMultiCaster.unsavedDocumentsDropped();
}
private boolean fireBeforeFileContentReload(final VirtualFile file, @NotNull Document document) {
for (FileDocumentSynchronizationVetoer vetoer : Extensions.getExtensions(FileDocumentSynchronizationVetoer.EP_NAME)) {
try {
if (!vetoer.mayReloadFileContent(file, document)) {
return false;
}
}
catch (Exception e) {
LOG.error(e);
}
}
myMultiCaster.beforeFileContentReload(file, document);
return true;
}
@NotNull
private static FileDocumentManagerListener[] getListeners() {
return FileDocumentManagerListener.EP_NAME.getExtensions();
}
private void handleErrorsOnSave(@NotNull Map<Document, IOException> failures) {
if (ApplicationManager.getApplication().isUnitTestMode()) {
IOException ioException = failures.isEmpty() ? null : failures.values().iterator().next();
if (ioException != null) {
throw new RuntimeException(ioException);
}
return;
}
for (IOException exception : failures.values()) {
LOG.warn(exception);
}
final String text = StringUtil.join(failures.values(), new Function<IOException, String>() {
@Override
public String fun(IOException e) {
return e.getMessage();
}
}, "\n");
final DialogWrapper dialog = new DialogWrapper(null) {
{
init();
setTitle(UIBundle.message("cannot.save.files.dialog.title"));
}
@Override
protected void createDefaultActions() {
super.createDefaultActions();
myOKAction.putValue(Action.NAME, UIBundle.message(myOnClose ? "cannot.save.files.dialog.ignore.changes" : "cannot.save.files.dialog.revert.changes"));
myOKAction.putValue(DEFAULT_ACTION, null);
if (!myOnClose) {
myCancelAction.putValue(Action.NAME, CommonBundle.getCloseButtonText());
}
}
@Override
protected JComponent createCenterPanel() {
final JPanel panel = new JPanel(new BorderLayout(0, 5));
panel.add(new JLabel(UIBundle.message("cannot.save.files.dialog.message")), BorderLayout.NORTH);
final JTextPane area = new JTextPane();
area.setText(text);
area.setEditable(false);
area.setMinimumSize(new Dimension(area.getMinimumSize().width, 50));
panel.add(new JBScrollPane(area, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER), BorderLayout.CENTER);
return panel;
}
};
dialog.show();
if (dialog.isOK()) {
for (Document document : failures.keySet()) {
reloadFromDisk(document);
}
}
}
}