blob: c3d1ab0c66eca45f4e1cc597e301d544773f3f0b [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.vcs.impl;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.components.ProjectComponent;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.editor.colors.ColorKey;
import com.intellij.openapi.editor.event.DocumentAdapter;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.project.DumbAwareRunnable;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.impl.DirectoryIndex;
import com.intellij.openapi.startup.StartupManager;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.NotNullLazyValue;
import com.intellij.openapi.vcs.FileStatus;
import com.intellij.openapi.vcs.FileStatusListener;
import com.intellij.openapi.vcs.FileStatusManager;
import com.intellij.openapi.vfs.VFileProperty;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.testFramework.LightVirtualFile;
import com.intellij.util.ThreeState;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.HashMap;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* @author mike
*/
public class FileStatusManagerImpl extends FileStatusManager implements ProjectComponent {
private final Map<VirtualFile, FileStatus> myCachedStatuses = Collections.synchronizedMap(new HashMap<VirtualFile, FileStatus>());
private final Map<VirtualFile, Boolean> myWhetherExactlyParentToChanged =
Collections.synchronizedMap(new HashMap<VirtualFile, Boolean>());
private final Project myProject;
private final List<FileStatusListener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList();
private FileStatusProvider myFileStatusProvider;
private final NotNullLazyValue<FileStatusProvider[]> myExtensions = new NotNullLazyValue<FileStatusProvider[]>() {
@NotNull
@Override
protected FileStatusProvider[] compute() {
return Extensions.getExtensions(FileStatusProvider.EP_NAME, myProject);
}
};
private static class FileStatusNull implements FileStatus {
private static final FileStatus INSTANCE = new FileStatusNull();
@Override
public String getText() {
throw new AssertionError("Should not be called");
}
@Override
public Color getColor() {
throw new AssertionError("Should not be called");
}
@NotNull
@Override
public ColorKey getColorKey() {
throw new AssertionError("Should not be called");
}
@NotNull
@Override
public String getId() {
throw new AssertionError("Should not be called");
}
}
public FileStatusManagerImpl(Project project, StartupManager startupManager,
@SuppressWarnings("UnusedParameters") DirectoryIndex makeSureIndexIsInitializedFirst) {
myProject = project;
startupManager.registerPreStartupActivity(new Runnable() {
@Override
public void run() {
DocumentAdapter documentListener = new DocumentAdapter() {
@Override
public void documentChanged(DocumentEvent event) {
VirtualFile file = FileDocumentManager.getInstance().getFile(event.getDocument());
if (file != null) {
refreshFileStatusFromDocument(file, event.getDocument());
}
}
};
final EditorFactory factory = EditorFactory.getInstance();
if (factory != null) {
factory.getEventMulticaster().addDocumentListener(documentListener, myProject);
}
}
});
startupManager.registerPostStartupActivity(new DumbAwareRunnable() {
@Override
public void run() {
fileStatusesChanged();
}
});
}
public void setFileStatusProvider(final FileStatusProvider fileStatusProvider) {
myFileStatusProvider = fileStatusProvider;
}
public FileStatus calcStatus(@NotNull final VirtualFile virtualFile) {
for (FileStatusProvider extension : myExtensions.getValue()) {
final FileStatus status = extension.getFileStatus(virtualFile);
if (status != null) {
return status;
}
}
if (virtualFile.isInLocalFileSystem() && myFileStatusProvider != null) {
return myFileStatusProvider.getFileStatus(virtualFile);
}
return getDefaultStatus(virtualFile);
}
@NotNull
public static FileStatus getDefaultStatus(@NotNull final VirtualFile file) {
return file.isValid() && file.is(VFileProperty.SPECIAL) ? FileStatus.IGNORED : FileStatus.NOT_CHANGED;
}
@Override
public void projectClosed() {
}
@Override
public void projectOpened() {
}
@Override
public void disposeComponent() {
myCachedStatuses.clear();
}
@Override
@NotNull
public String getComponentName() {
return "FileStatusManager";
}
@Override
public void initComponent() {
}
@Override
public void addFileStatusListener(@NotNull FileStatusListener listener) {
myListeners.add(listener);
}
@Override
public void addFileStatusListener(@NotNull final FileStatusListener listener, @NotNull Disposable parentDisposable) {
addFileStatusListener(listener);
Disposer.register(parentDisposable, new Disposable() {
@Override
public void dispose() {
removeFileStatusListener(listener);
}
});
}
@Override
public void fileStatusesChanged() {
if (myProject.isDisposed()) {
return;
}
if (!ApplicationManager.getApplication().isDispatchThread()) {
ApplicationManager.getApplication().invokeLater(new DumbAwareRunnable() {
@Override
public void run() {
fileStatusesChanged();
}
}, ModalityState.NON_MODAL);
return;
}
myCachedStatuses.clear();
myWhetherExactlyParentToChanged.clear();
for (FileStatusListener listener : myListeners) {
listener.fileStatusesChanged();
}
}
private void cacheChangedFileStatus(final VirtualFile virtualFile, final FileStatus fs) {
myCachedStatuses.put(virtualFile, fs);
if (FileStatus.NOT_CHANGED.equals(fs)) {
final ThreeState parentingStatus = myFileStatusProvider.getNotChangedDirectoryParentingStatus(virtualFile);
if (ThreeState.YES.equals(parentingStatus)) {
myWhetherExactlyParentToChanged.put(virtualFile, true);
}
else if (ThreeState.UNSURE.equals(parentingStatus)) {
myWhetherExactlyParentToChanged.put(virtualFile, false);
}
}
else {
myWhetherExactlyParentToChanged.remove(virtualFile);
}
}
@Override
public void fileStatusChanged(final VirtualFile file) {
final Application application = ApplicationManager.getApplication();
if (!application.isDispatchThread() && !application.isUnitTestMode()) {
ApplicationManager.getApplication().invokeLater(new DumbAwareRunnable() {
@Override
public void run() {
fileStatusChanged(file);
}
});
return;
}
if (file == null || !file.isValid()) return;
FileStatus cachedStatus = getCachedStatus(file);
if (cachedStatus == FileStatusNull.INSTANCE) {
return;
}
if (cachedStatus == null) {
cacheChangedFileStatus(file, FileStatusNull.INSTANCE);
return;
}
FileStatus newStatus = calcStatus(file);
if (cachedStatus == newStatus) return;
cacheChangedFileStatus(file, newStatus);
for (FileStatusListener listener : myListeners) {
listener.fileStatusChanged(file);
}
}
@Override
public FileStatus getStatus(@NotNull final VirtualFile file) {
if (file instanceof LightVirtualFile) {
return FileStatus.NOT_CHANGED; // do not leak light files via cache
}
FileStatus status = getCachedStatus(file);
if (status == null || status == FileStatusNull.INSTANCE) {
status = calcStatus(file);
cacheChangedFileStatus(file, status);
}
return status;
}
public FileStatus getCachedStatus(final VirtualFile file) {
return myCachedStatuses.get(file);
}
@Override
public void removeFileStatusListener(@NotNull FileStatusListener listener) {
myListeners.remove(listener);
}
@Override
public Color getNotChangedDirectoryColor(@NotNull VirtualFile vf) {
final Color notChangedColor = FileStatus.NOT_CHANGED.getColor();
if (!vf.isDirectory()) {
return notChangedColor;
}
final Boolean exactMatch = myWhetherExactlyParentToChanged.get(vf);
return exactMatch == null
? notChangedColor
: exactMatch ? FileStatus.NOT_CHANGED_IMMEDIATE.getColor() : FileStatus.NOT_CHANGED_RECURSIVE.getColor();
}
public void refreshFileStatusFromDocument(final VirtualFile file, final Document doc) {
if (myFileStatusProvider != null) {
myFileStatusProvider.refreshFileStatusFromDocument(file, doc);
}
}
}