/*
* Copyright 2000-2016 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.actions;
import com.intellij.diff.DiffContext;
import com.intellij.diff.DiffContextEx;
import com.intellij.diff.DiffExtension;
import com.intellij.diff.FrameDiffTool.DiffViewer;
import com.intellij.diff.contents.DiffContent;
import com.intellij.diff.contents.FileContent;
import com.intellij.diff.merge.TextMergeViewer;
import com.intellij.diff.requests.ContentDiffRequest;
import com.intellij.diff.requests.DiffRequest;
import com.intellij.diff.tools.fragmented.UnifiedDiffViewer;
import com.intellij.diff.tools.simple.SimpleThreesideDiffViewer;
import com.intellij.diff.tools.simple.ThreesideTextDiffViewerEx;
import com.intellij.diff.tools.util.DiffDataKeys;
import com.intellij.diff.tools.util.base.DiffViewerBase;
import com.intellij.diff.tools.util.base.DiffViewerListener;
import com.intellij.diff.tools.util.side.OnesideTextDiffViewer;
import com.intellij.diff.tools.util.side.TwosideTextDiffViewer;
import com.intellij.diff.util.DiffUserDataKeysEx;
import com.intellij.diff.util.Side;
import com.intellij.diff.util.ThreeSide;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType;
import com.intellij.notification.impl.NotificationsManagerImpl;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.ToggleAction;
import com.intellij.openapi.actionSystem.ex.ActionUtil;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.localVcs.UpToDateLineNumberProvider;
import com.intellij.openapi.progress.util.BackgroundTaskUtil;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.Balloon;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vcs.*;
import com.intellij.openapi.vcs.annotate.AnnotationProvider;
import com.intellij.openapi.vcs.annotate.FileAnnotation;
import com.intellij.openapi.vcs.changes.*;
import com.intellij.openapi.vcs.changes.actions.diff.ChangeDiffRequestProducer;
import com.intellij.openapi.vcs.history.VcsRevisionNumber;
import com.intellij.openapi.vcs.impl.BackgroundableActionLock;
import com.intellij.openapi.vcs.impl.UpToDateLineNumberProviderImpl;
import com.intellij.openapi.vcs.impl.VcsBackgroundableActions;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.IdeFrame;
import com.intellij.ui.awt.RelativePoint;
import com.intellij.util.ObjectUtils;
import com.intellij.util.ui.UIUtil;
import com.intellij.vcs.AnnotationProviderEx;
import com.intellij.vcsUtil.VcsUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
public class AnnotateDiffViewerAction extends ToggleAction implements DumbAware {
private static final Logger LOG = Logger.getInstance(AnnotateDiffViewerAction.class);
private static final Key<boolean[]> ANNOTATIONS_SHOWN_KEY = Key.create("Diff.AnnotateAction.AnnotationShown");
private static final ViewerAnnotatorFactory[] ANNOTATORS = new ViewerAnnotatorFactory[]{
new TwosideAnnotatorFactory(), new OnesideAnnotatorFactory(), new UnifiedAnnotatorFactory(),
new ThreesideAnnotatorFactory(), new TextMergeAnnotatorFactory()
};
public AnnotateDiffViewerAction() {
ActionUtil.copyFrom(this, "Annotate");
setEnabledInModalContext(true);
}
@Override
public void update(AnActionEvent e) {
super.update(e);
boolean enabled = isEnabled(e);
e.getPresentation().setVisible(enabled);
e.getPresentation().setEnabled(enabled && !isSuspended(e));
}
@Nullable
@SuppressWarnings("unchecked")
private static ViewerAnnotator getAnnotator(@NotNull DiffViewerBase viewer, @NotNull Editor editor) {
for (ViewerAnnotatorFactory annotator : ANNOTATORS) {
if (annotator.getViewerClass().isInstance(viewer)) return annotator.createAnnotator(viewer, editor);
}
return null;
}
@Nullable
private static EventData collectEventData(AnActionEvent e) {
DiffViewerBase viewer = getViewer(e);
if (viewer == null) return null;
if (viewer.getProject() == null) return null;
if (viewer.isDisposed()) return null;
Editor editor = e.getData(CommonDataKeys.EDITOR);
if (editor == null) return null;
ViewerAnnotator annotator = getAnnotator(viewer, editor);
if (annotator == null) return null;
return new EventData(viewer, annotator);
}
@Nullable
private static DiffViewerBase getViewer(AnActionEvent e) {
DiffViewerBase diffViewer = ObjectUtils.tryCast(e.getData(DiffDataKeys.DIFF_VIEWER), DiffViewerBase.class);
if (diffViewer != null) return diffViewer;
TextMergeViewer mergeViewer = ObjectUtils.tryCast(e.getData(DiffDataKeys.MERGE_VIEWER), TextMergeViewer.class);
if (mergeViewer != null) return mergeViewer.getViewer();
return null;
}
private static boolean isEnabled(AnActionEvent e) {
EventData data = collectEventData(e);
if (data == null) return false;
if (data.annotator.isAnnotationShown()) return true;
return data.annotator.createAnnotationsLoader() != null;
}
private static boolean isSuspended(AnActionEvent e) {
EventData data = collectEventData(e);
return data != null && data.annotator.getBackgroundableLock().isLocked();
}
private static boolean isAnnotated(AnActionEvent e) {
EventData data = collectEventData(e);
assert data != null;
return data.annotator.isAnnotationShown();
}
private static void perform(AnActionEvent e, boolean selected) {
EventData data = collectEventData(e);
assert data != null;
boolean annotationShown = data.annotator.isAnnotationShown();
if (annotationShown) {
data.annotator.hideAnnotation();
}
else {
doAnnotate(data.annotator);
}
}
@Override
public boolean isSelected(AnActionEvent e) {
EventData data = collectEventData(e);
return data != null && data.annotator.isAnnotationShown();
}
@Override
public void setSelected(AnActionEvent e, boolean state) {
perform(e, state);
}
private static void doAnnotate(@NotNull final ViewerAnnotator annotator) {
final DiffViewerBase viewer = annotator.getViewer();
final Project project = viewer.getProject();
if (project == null) return;
final FileAnnotationLoader loader = annotator.createAnnotationsLoader();
if (loader == null) return;
final DiffContextEx diffContext = ObjectUtils.tryCast(viewer.getContext(), DiffContextEx.class);
annotator.getBackgroundableLock().lock();
if (diffContext != null) diffContext.showProgressBar(true);
BackgroundTaskUtil.executeOnPooledThread(indicator -> {
try {
loader.run();
}
finally {
ApplicationManager.getApplication().invokeLater(() -> {
if (diffContext != null) diffContext.showProgressBar(false);
annotator.getBackgroundableLock().unlock();
VcsException exception = loader.getException();
if (exception != null) {
Notification notification = VcsNotifier.IMPORTANT_ERROR_NOTIFICATION
.createNotification("Can't Load Annotations", exception.getMessage(), NotificationType.ERROR, null);
showNotification(viewer, notification);
LOG.warn(exception);
return;
}
if (loader.getResult() == null) return;
if (viewer.isDisposed()) return;
annotator.showAnnotation(loader.getResult());
}, indicator.getModalityState());
}
}, viewer);
}
@Nullable
private static FileAnnotationLoader createThreesideAnnotationsLoader(@NotNull Project project,
@NotNull DiffRequest request,
@NotNull ThreeSide side) {
if (request instanceof ContentDiffRequest) {
ContentDiffRequest requestEx = (ContentDiffRequest)request;
if (requestEx.getContents().size() == 3) {
DiffContent content = side.select(requestEx.getContents());
FileAnnotationLoader loader = createAnnotationsLoader(project, content);
if (loader != null) return loader;
}
}
return null;
}
@Nullable
private static FileAnnotationLoader createTwosideAnnotationsLoader(@NotNull Project project,
@NotNull DiffRequest request,
@NotNull Side side) {
Change change = request.getUserData(ChangeDiffRequestProducer.CHANGE_KEY);
if (change != null) {
ContentRevision revision = side.select(change.getBeforeRevision(), change.getAfterRevision());
if (revision != null) {
AbstractVcs vcs = ChangesUtil.getVcsForChange(change, project);
if (revision instanceof CurrentContentRevision) {
VirtualFile file = ((CurrentContentRevision)revision).getVirtualFile();
FileAnnotationLoader loader = doCreateAnnotationsLoader(project, vcs, file);
if (loader != null) return loader;
}
else {
FileAnnotationLoader loader = doCreateAnnotationsLoader(vcs, revision.getFile(), revision.getRevisionNumber());
if (loader != null) return loader;
}
}
}
if (request instanceof ContentDiffRequest) {
ContentDiffRequest requestEx = (ContentDiffRequest)request;
if (requestEx.getContents().size() == 2) {
DiffContent content = side.select(requestEx.getContents());
return createAnnotationsLoader(project, content);
}
}
return null;
}
@Nullable
private static FileAnnotationLoader createAnnotationsLoader(@NotNull Project project, @NotNull DiffContent content) {
if (content instanceof FileContent) {
VirtualFile file = ((FileContent)content).getFile();
AbstractVcs vcs = VcsUtil.getVcsFor(project, file);
FileAnnotationLoader loader = doCreateAnnotationsLoader(project, vcs, file);
if (loader != null) return loader;
}
Pair<FilePath, VcsRevisionNumber> info = content.getUserData(DiffUserDataKeysEx.REVISION_INFO);
if (info != null) {
FilePath filePath = info.first;
AbstractVcs vcs = VcsUtil.getVcsFor(project, filePath);
FileAnnotationLoader loader = doCreateAnnotationsLoader(vcs, filePath, info.second);
if (loader != null) return loader;
}
return null;
}
@Nullable
private static FileAnnotationLoader doCreateAnnotationsLoader(@NotNull Project project,
@Nullable AbstractVcs vcs,
@Nullable final VirtualFile file) {
if (vcs == null || file == null) return null;
final AnnotationProvider annotationProvider = vcs.getAnnotationProvider();
if (annotationProvider == null) return null;
FileStatus fileStatus = FileStatusManager.getInstance(project).getStatus(file);
if (fileStatus == FileStatus.UNKNOWN || fileStatus == FileStatus.ADDED || fileStatus == FileStatus.IGNORED) {
return null;
}
return new FileAnnotationLoader(vcs) {
@Override
public FileAnnotation compute() throws VcsException {
return annotationProvider.annotate(file);
}
};
}
@Nullable
private static FileAnnotationLoader doCreateAnnotationsLoader(@Nullable AbstractVcs vcs,
@Nullable final FilePath path,
@Nullable final VcsRevisionNumber revisionNumber) {
if (vcs == null || path == null || revisionNumber == null) return null;
if (revisionNumber instanceof TextRevisionNumber) return null;
final AnnotationProvider annotationProvider = vcs.getAnnotationProvider();
if (!(annotationProvider instanceof AnnotationProviderEx)) return null;
return new FileAnnotationLoader(vcs) {
@Override
public FileAnnotation compute() throws VcsException {
return ((AnnotationProviderEx)annotationProvider).annotate(path, revisionNumber);
}
};
}
public static class MyDiffExtension extends DiffExtension {
@Override
public void onViewerCreated(@NotNull DiffViewer diffViewer, @NotNull DiffContext context, @NotNull DiffRequest request) {
if (diffViewer instanceof DiffViewerBase) {
DiffViewerBase viewer = (DiffViewerBase)diffViewer;
viewer.addListener(new MyDiffViewerListener(viewer));
}
}
}
private static class MyDiffViewerListener extends DiffViewerListener {
@NotNull private final DiffViewerBase myViewer;
public MyDiffViewerListener(@NotNull DiffViewerBase viewer) {
myViewer = viewer;
}
@Override
@SuppressWarnings("unchecked")
public void onInit() {
if (myViewer.getProject() == null) return;
for (ViewerAnnotatorFactory annotator : ANNOTATORS) {
if (annotator.getViewerClass().isInstance(myViewer)) annotator.showRememberedAnnotations(myViewer);
}
}
@Override
@SuppressWarnings("unchecked")
public void onDispose() {
if (myViewer.getProject() == null) return;
for (ViewerAnnotatorFactory annotator : ANNOTATORS) {
if (annotator.getViewerClass().isInstance(myViewer)) annotator.rememberShownAnnotations(myViewer);
}
}
}
private static void showNotification(@NotNull DiffViewerBase viewer, @NotNull Notification notification) {
JComponent component = viewer.getComponent();
Window window = UIUtil.getWindow(component);
if (window instanceof IdeFrame && NotificationsManagerImpl.findWindowForBalloon(viewer.getProject()) == window) {
notification.notify(viewer.getProject());
return;
}
Balloon balloon = NotificationsManagerImpl.createBalloon(component, notification, false, true, null, viewer);
Dimension componentSize = component.getSize();
Dimension balloonSize = balloon.getPreferredSize();
int width = Math.min(balloonSize.width, componentSize.width);
int height = Math.min(balloonSize.height, componentSize.height);
// top-right corner, 20px to the edges
RelativePoint point = new RelativePoint(component, new Point(componentSize.width - 20 - width / 2, 20 + height / 2));
balloon.show(point, Balloon.Position.above);
}
private static class TwosideAnnotatorFactory extends TwosideViewerAnnotatorFactory<TwosideTextDiffViewer> {
@Override
@NotNull
public Class<TwosideTextDiffViewer> getViewerClass() {
return TwosideTextDiffViewer.class;
}
@Override
@Nullable
public Side getCurrentSide(@NotNull TwosideTextDiffViewer viewer, @NotNull Editor editor) {
Side side = null; // we can't just use getCurrentSide() here, popup can be called on unfocused editor
if (viewer.getEditor(Side.LEFT) == editor) side = Side.LEFT;
if (viewer.getEditor(Side.RIGHT) == editor) side = Side.RIGHT;
return side;
}
@Override
public boolean isAnnotationShown(@NotNull TwosideTextDiffViewer viewer, @NotNull Side side) {
return viewer.getEditor(side).getGutter().isAnnotationsShown();
}
@Override
public void showAnnotation(@NotNull TwosideTextDiffViewer viewer, @NotNull Side side, @NotNull AnnotationData data) {
Project project = ObjectUtils.assertNotNull(viewer.getProject());
AnnotateToggleAction.doAnnotate(viewer.getEditor(side), project, null, data.annotation, data.vcs);
}
@Override
public void hideAnnotation(@NotNull TwosideTextDiffViewer viewer, @NotNull Side side) {
viewer.getEditor(side).getGutter().closeAllAnnotations();
}
}
private static class OnesideAnnotatorFactory extends TwosideViewerAnnotatorFactory<OnesideTextDiffViewer> {
@Override
@NotNull
public Class<OnesideTextDiffViewer> getViewerClass() {
return OnesideTextDiffViewer.class;
}
@Override
@Nullable
public Side getCurrentSide(@NotNull OnesideTextDiffViewer viewer, @NotNull Editor editor) {
if (viewer.getEditor() != editor) return null;
return viewer.getSide();
}
@Override
public boolean isAnnotationShown(@NotNull OnesideTextDiffViewer viewer, @NotNull Side side) {
if (side != viewer.getSide()) return false;
return viewer.getEditor().getGutter().isAnnotationsShown();
}
@Override
public void showAnnotation(@NotNull OnesideTextDiffViewer viewer, @NotNull Side side, @NotNull AnnotationData data) {
if (side != viewer.getSide()) return;
Project project = ObjectUtils.assertNotNull(viewer.getProject());
AnnotateToggleAction.doAnnotate(viewer.getEditor(), project, null, data.annotation, data.vcs);
}
@Override
public void hideAnnotation(@NotNull OnesideTextDiffViewer viewer, @NotNull Side side) {
viewer.getEditor().getGutter().closeAllAnnotations();
}
}
private static class UnifiedAnnotatorFactory extends TwosideViewerAnnotatorFactory<UnifiedDiffViewer> {
@Override
@NotNull
public Class<UnifiedDiffViewer> getViewerClass() {
return UnifiedDiffViewer.class;
}
@Override
@Nullable
public Side getCurrentSide(@NotNull UnifiedDiffViewer viewer, @NotNull Editor editor) {
if (viewer.getEditor() != editor) return null;
return viewer.getMasterSide();
}
@Override
public boolean isAnnotationShown(@NotNull UnifiedDiffViewer viewer, @NotNull Side side) {
if (side != viewer.getMasterSide()) return false;
return viewer.getEditor().getGutter().isAnnotationsShown();
}
@Override
public void showAnnotation(@NotNull UnifiedDiffViewer viewer, @NotNull Side side, @NotNull AnnotationData data) {
if (side != viewer.getMasterSide()) return;
Project project = ObjectUtils.assertNotNull(viewer.getProject());
UnifiedUpToDateLineNumberProvider lineNumberProvider = new UnifiedUpToDateLineNumberProvider(viewer, side);
AnnotateToggleAction.doAnnotate(viewer.getEditor(), project, null, data.annotation, data.vcs, lineNumberProvider);
}
@Override
public void hideAnnotation(@NotNull UnifiedDiffViewer viewer, @NotNull Side side) {
viewer.getEditor().getGutter().closeAllAnnotations();
}
}
private static class UnifiedUpToDateLineNumberProvider implements UpToDateLineNumberProvider {
@NotNull private final UnifiedDiffViewer myViewer;
@NotNull private final Side mySide;
@NotNull private final UpToDateLineNumberProvider myLocalChangesProvider;
public UnifiedUpToDateLineNumberProvider(@NotNull UnifiedDiffViewer viewer, @NotNull Side side) {
myViewer = viewer;
mySide = side;
myLocalChangesProvider = new UpToDateLineNumberProviderImpl(myViewer.getDocument(mySide), viewer.getProject());
}
@Override
public int getLineNumber(int currentNumber) {
int number = myViewer.transferLineFromOnesideStrict(mySide, currentNumber);
return number != -1 ? myLocalChangesProvider.getLineNumber(number) : FAKE_LINE_NUMBER;
}
@Override
public boolean isLineChanged(int currentNumber) {
return getLineNumber(currentNumber) == ABSENT_LINE_NUMBER;
}
@Override
public boolean isRangeChanged(int start, int end) {
int line1 = myViewer.transferLineFromOnesideStrict(mySide, start);
int line2 = myViewer.transferLineFromOnesideStrict(mySide, end);
if (line2 - line1 != end - start) return true;
for (int i = start; i <= end; i++) {
if (isLineChanged(i)) return true; // TODO: a single request to LineNumberConvertor
}
return myLocalChangesProvider.isRangeChanged(line1, line2);
}
@Override
public int getLineCount() {
return myLocalChangesProvider.getLineCount();
}
}
private static class ThreesideAnnotatorFactory extends ThreesideViewerAnnotatorFactory<ThreesideTextDiffViewerEx> {
@Override
@NotNull
public Class<? extends ThreesideTextDiffViewerEx> getViewerClass() {
return SimpleThreesideDiffViewer.class;
}
@Override
@Nullable
public ThreeSide getCurrentSide(@NotNull ThreesideTextDiffViewerEx viewer, @NotNull Editor editor) {
ThreeSide side = null; // we can't just use getCurrentSide() here, popup can be called on unfocused editor
if (viewer.getEditor(ThreeSide.LEFT) == editor) side = ThreeSide.LEFT;
if (viewer.getEditor(ThreeSide.BASE) == editor) side = ThreeSide.BASE;
if (viewer.getEditor(ThreeSide.RIGHT) == editor) side = ThreeSide.RIGHT;
return side;
}
@Override
public boolean isAnnotationShown(@NotNull ThreesideTextDiffViewerEx viewer, @NotNull ThreeSide side) {
return viewer.getEditor(side).getGutter().isAnnotationsShown();
}
@Override
public void showAnnotation(@NotNull ThreesideTextDiffViewerEx viewer, @NotNull ThreeSide side, @NotNull AnnotationData data) {
Project project = ObjectUtils.assertNotNull(viewer.getProject());
AnnotateToggleAction.doAnnotate(viewer.getEditor(side), project, null, data.annotation, data.vcs);
}
@Override
public void hideAnnotation(@NotNull ThreesideTextDiffViewerEx viewer, @NotNull ThreeSide side) {
viewer.getEditor(side).getGutter().closeAllAnnotations();
}
}
private static class TextMergeAnnotatorFactory extends ThreesideAnnotatorFactory {
@Override
@NotNull
public Class<? extends ThreesideTextDiffViewerEx> getViewerClass() {
return TextMergeViewer.MyThreesideViewer.class;
}
@Nullable
@Override
public ViewerAnnotator createAnnotator(@NotNull ThreesideTextDiffViewerEx viewer, @NotNull ThreeSide side) {
if (side == ThreeSide.BASE) return null; // middle content is local Document, not the BASE one
return super.createAnnotator(viewer, side);
}
}
private static abstract class TwosideViewerAnnotatorFactory<T extends DiffViewerBase> extends ViewerAnnotatorFactory<T> {
@Nullable
public abstract Side getCurrentSide(@NotNull T viewer, @NotNull Editor editor);
public abstract boolean isAnnotationShown(@NotNull T viewer, @NotNull Side side);
public abstract void showAnnotation(@NotNull T viewer, @NotNull Side side, @NotNull AnnotationData data);
public abstract void hideAnnotation(@NotNull T viewer, @NotNull Side side);
@Override
@Nullable
public ViewerAnnotator createAnnotator(@NotNull T viewer, @NotNull Editor editor) {
Side side = getCurrentSide(viewer, editor);
if (side == null) return null;
return createAnnotator(viewer, side);
}
@Override
public void showRememberedAnnotations(@NotNull T viewer) {
boolean[] annotationsShown = viewer.getRequest().getUserData(ANNOTATIONS_SHOWN_KEY);
if (annotationsShown == null || annotationsShown.length != 2) return;
if (annotationsShown[0]) {
ViewerAnnotator annotator = createAnnotator(viewer, Side.LEFT);
if (annotator != null) doAnnotate(annotator);
}
if (annotationsShown[1]) {
ViewerAnnotator annotator = createAnnotator(viewer, Side.RIGHT);
if (annotator != null) doAnnotate(annotator);
}
}
@Override
public void rememberShownAnnotations(@NotNull T viewer) {
boolean[] annotationsShown = new boolean[2];
annotationsShown[0] = isAnnotationShown(viewer, Side.LEFT);
annotationsShown[1] = isAnnotationShown(viewer, Side.RIGHT);
viewer.getRequest().putUserData(ANNOTATIONS_SHOWN_KEY, annotationsShown);
}
@Nullable
public ViewerAnnotator createAnnotator(@NotNull T viewer, @NotNull Side side) {
TwosideViewerAnnotatorFactory<T> factory = this;
Project project = viewer.getProject();
assert project != null;
return new ViewerAnnotator() {
@NotNull
@Override
public T getViewer() {
return viewer;
}
@Override
public boolean isAnnotationShown() {
return factory.isAnnotationShown(viewer, side);
}
@Override
public void showAnnotation(@NotNull AnnotationData data) {
factory.showAnnotation(viewer, side, data);
}
@Override
public void hideAnnotation() {
factory.hideAnnotation(viewer, side);
}
@Nullable
@Override
public FileAnnotationLoader createAnnotationsLoader() {
return createTwosideAnnotationsLoader(project, viewer.getRequest(), side);
}
@NotNull
public BackgroundableActionLock getBackgroundableLock() {
return BackgroundableActionLock.getLock(viewer.getProject(), VcsBackgroundableActions.ANNOTATE, viewer, side);
}
};
}
}
private static abstract class ThreesideViewerAnnotatorFactory<T extends DiffViewerBase> extends ViewerAnnotatorFactory<T> {
@Nullable
public abstract ThreeSide getCurrentSide(@NotNull T viewer, @NotNull Editor editor);
public abstract boolean isAnnotationShown(@NotNull T viewer, @NotNull ThreeSide side);
public abstract void showAnnotation(@NotNull T viewer, @NotNull ThreeSide side, @NotNull AnnotationData data);
public abstract void hideAnnotation(@NotNull T viewer, @NotNull ThreeSide side);
@Override
@Nullable
public ViewerAnnotator createAnnotator(@NotNull T viewer, @NotNull Editor editor) {
ThreeSide side = getCurrentSide(viewer, editor);
if (side == null) return null;
return createAnnotator(viewer, side);
}
@Override
public void showRememberedAnnotations(@NotNull T viewer) {
boolean[] annotationsShown = viewer.getRequest().getUserData(ANNOTATIONS_SHOWN_KEY);
if (annotationsShown == null || annotationsShown.length != 3) return;
if (annotationsShown[0]) {
ViewerAnnotator annotator = createAnnotator(viewer, ThreeSide.LEFT);
if (annotator != null) doAnnotate(annotator);
}
if (annotationsShown[1]) {
ViewerAnnotator annotator = createAnnotator(viewer, ThreeSide.BASE);
if (annotator != null) doAnnotate(annotator);
}
if (annotationsShown[2]) {
ViewerAnnotator annotator = createAnnotator(viewer, ThreeSide.RIGHT);
if (annotator != null) doAnnotate(annotator);
}
}
@Override
public void rememberShownAnnotations(@NotNull T viewer) {
boolean[] annotationsShown = new boolean[3];
annotationsShown[0] = isAnnotationShown(viewer, ThreeSide.LEFT);
annotationsShown[1] = isAnnotationShown(viewer, ThreeSide.BASE);
annotationsShown[2] = isAnnotationShown(viewer, ThreeSide.RIGHT);
viewer.getRequest().putUserData(ANNOTATIONS_SHOWN_KEY, annotationsShown);
}
@Nullable
public ViewerAnnotator createAnnotator(@NotNull T viewer, @NotNull ThreeSide side) {
ThreesideViewerAnnotatorFactory<T> factory = this;
Project project = viewer.getProject();
assert project != null;
return new ViewerAnnotator() {
@NotNull
@Override
public T getViewer() {
return viewer;
}
@Override
public boolean isAnnotationShown() {
return factory.isAnnotationShown(viewer, side);
}
@Override
public void showAnnotation(@NotNull AnnotationData data) {
factory.showAnnotation(viewer, side, data);
}
@Override
public void hideAnnotation() {
factory.hideAnnotation(viewer, side);
}
@Nullable
@Override
public FileAnnotationLoader createAnnotationsLoader() {
return createThreesideAnnotationsLoader(project, viewer.getRequest(), side);
}
@NotNull
public BackgroundableActionLock getBackgroundableLock() {
return BackgroundableActionLock.getLock(viewer.getProject(), VcsBackgroundableActions.ANNOTATE, viewer, side);
}
};
}
}
private static abstract class ViewerAnnotatorFactory<T extends DiffViewerBase> {
@NotNull
public abstract Class<? extends T> getViewerClass();
@Nullable
public abstract ViewerAnnotator createAnnotator(@NotNull T viewer, @NotNull Editor editor);
public abstract void showRememberedAnnotations(@NotNull T viewer);
public abstract void rememberShownAnnotations(@NotNull T viewer);
}
private static abstract class ViewerAnnotator {
@NotNull
public abstract DiffViewerBase getViewer();
public abstract boolean isAnnotationShown();
public abstract void showAnnotation(@NotNull AnnotationData data);
public abstract void hideAnnotation();
@Nullable
public abstract FileAnnotationLoader createAnnotationsLoader();
@NotNull
public abstract BackgroundableActionLock getBackgroundableLock();
}
private abstract static class FileAnnotationLoader {
@NotNull private final AbstractVcs myVcs;
@Nullable private VcsException myException;
@Nullable private FileAnnotation myResult;
public FileAnnotationLoader(@NotNull AbstractVcs vcs) {
myVcs = vcs;
}
@Nullable
public VcsException getException() {
return myException;
}
@Nullable
public AnnotationData getResult() {
return myResult != null ? new AnnotationData(myVcs, myResult) : null;
}
public void run() {
try {
myResult = compute();
}
catch (VcsException e) {
myException = e;
}
}
protected abstract FileAnnotation compute() throws VcsException;
}
private static class AnnotationData {
@NotNull public final AbstractVcs vcs;
@NotNull public final FileAnnotation annotation;
public AnnotationData(@NotNull AbstractVcs vcs, @NotNull FileAnnotation annotation) {
this.vcs = vcs;
this.annotation = annotation;
}
}
private static class EventData {
@NotNull public final DiffViewerBase viewer;
@NotNull public final ViewerAnnotator annotator;
public EventData(@NotNull DiffViewerBase viewer, @NotNull ViewerAnnotator annotator) {
this.viewer = viewer;
this.annotator = annotator;
}
}
public static class Provider implements AnnotateToggleAction.Provider {
@Override
public boolean isEnabled(AnActionEvent e) {
return AnnotateDiffViewerAction.isEnabled(e);
}
@Override
public boolean isSuspended(AnActionEvent e) {
return AnnotateDiffViewerAction.isSuspended(e);
}
@Override
public boolean isAnnotated(AnActionEvent e) {
return AnnotateDiffViewerAction.isAnnotated(e);
}
@Override
public void perform(AnActionEvent e, boolean selected) {
AnnotateDiffViewerAction.perform(e, selected);
}
}
}