d348ae3a37e373de5f1796badd730e2873a1f41f
[idea/community.git] / platform / vcs-impl / src / com / intellij / openapi / vcs / actions / AnnotateDiffViewerAction.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.openapi.vcs.actions;
17
18 import com.intellij.diff.FrameDiffTool.DiffViewer;
19 import com.intellij.diff.contents.DiffContent;
20 import com.intellij.diff.contents.FileContent;
21 import com.intellij.diff.requests.ContentDiffRequest;
22 import com.intellij.diff.requests.DiffRequest;
23 import com.intellij.diff.tools.fragmented.UnifiedDiffViewer;
24 import com.intellij.diff.tools.util.DiffDataKeys;
25 import com.intellij.diff.tools.util.base.DiffViewerBase;
26 import com.intellij.diff.tools.util.side.OnesideTextDiffViewer;
27 import com.intellij.diff.tools.util.side.TwosideTextDiffViewer;
28 import com.intellij.diff.util.Side;
29 import com.intellij.icons.AllIcons;
30 import com.intellij.openapi.actionSystem.AnActionEvent;
31 import com.intellij.openapi.actionSystem.CommonDataKeys;
32 import com.intellij.openapi.editor.Editor;
33 import com.intellij.openapi.localVcs.UpToDateLineNumberProvider;
34 import com.intellij.openapi.progress.ProgressIndicator;
35 import com.intellij.openapi.progress.ProgressManager;
36 import com.intellij.openapi.progress.Task;
37 import com.intellij.openapi.project.DumbAwareAction;
38 import com.intellij.openapi.project.Project;
39 import com.intellij.openapi.util.Pair;
40 import com.intellij.openapi.vcs.*;
41 import com.intellij.openapi.vcs.annotate.AnnotationProvider;
42 import com.intellij.openapi.vcs.annotate.FileAnnotation;
43 import com.intellij.openapi.vcs.changes.*;
44 import com.intellij.openapi.vcs.changes.actions.diff.ChangeDiffRequestProducer;
45 import com.intellij.openapi.vcs.impl.BackgroundableActionEnabledHandler;
46 import com.intellij.openapi.vcs.impl.ProjectLevelVcsManagerImpl;
47 import com.intellij.openapi.vcs.impl.UpToDateLineNumberProviderImpl;
48 import com.intellij.openapi.vcs.impl.VcsBackgroundableActions;
49 import com.intellij.openapi.vfs.VirtualFile;
50 import com.intellij.util.ObjectUtils;
51 import com.intellij.vcs.AnnotationProviderEx;
52 import com.intellij.vcsUtil.VcsUtil;
53 import org.jetbrains.annotations.NotNull;
54 import org.jetbrains.annotations.Nullable;
55
56 public class AnnotateDiffViewerAction extends DumbAwareAction {
57   private static final ViewerAnnotator[] ANNOTATORS = new ViewerAnnotator[]{
58     new TwosideAnnotator(), new OnesideAnnotator(), new UnifiedAnnotator()
59   };
60
61   public AnnotateDiffViewerAction() {
62     super("Annotate", null, AllIcons.Actions.Annotate);
63   }
64
65   @Override
66   public void update(AnActionEvent e) {
67     e.getPresentation().setEnabledAndVisible(isEnabled(e));
68   }
69
70   @Nullable
71   private static ViewerAnnotator getAnnotator(@NotNull DiffViewerBase viewer) {
72     for (ViewerAnnotator annotator : ANNOTATORS) {
73       if (annotator.getViewerClass().isInstance(viewer)) return annotator;
74     }
75     return null;
76   }
77
78   private static boolean isEnabled(AnActionEvent e) {
79     DiffViewerBase viewer = ObjectUtils.tryCast(e.getData(DiffDataKeys.DIFF_VIEWER), DiffViewerBase.class);
80     if (viewer == null) return false;
81     if (viewer.getProject() == null) return false;
82     if (viewer.isDisposed()) return false;
83
84     Editor editor = e.getData(CommonDataKeys.EDITOR);
85     if (editor == null) return false;
86
87     ViewerAnnotator annotator = getAnnotator(viewer);
88     if (annotator == null) return false;
89
90     //noinspection unchecked
91     Side side = annotator.getCurrentSide(viewer, editor);
92     if (side == null) return false;
93
94     //noinspection unchecked
95     if (annotator.isAnnotationShown(viewer, side)) return false;
96     if (checkRunningProgress(viewer, side)) return false;
97     return createAnnotationsLoader(viewer.getProject(), viewer.getRequest(), side) != null;
98   }
99
100   @Override
101   public void actionPerformed(final AnActionEvent e) {
102     DiffViewerBase viewer = (DiffViewerBase)e.getRequiredData(DiffDataKeys.DIFF_VIEWER);
103     Editor editor = e.getRequiredData(CommonDataKeys.EDITOR);
104
105     ViewerAnnotator annotator = getAnnotator(viewer);
106     assert annotator != null;
107
108     //noinspection unchecked
109     Side side = annotator.getCurrentSide(viewer, editor);
110     assert side != null;
111
112     doAnnotate(annotator, viewer, side);
113   }
114
115   public static <T extends DiffViewerBase> void doAnnotate(@NotNull final ViewerAnnotator<T> annotator,
116                                                            @NotNull final T viewer,
117                                                            @NotNull final Side side) {
118     final Project project = viewer.getProject();
119     assert project != null;
120
121     final FileAnnotationLoader loader = createAnnotationsLoader(project, viewer.getRequest(), side);
122     assert loader != null;
123
124     markRunningProgress(viewer, side, true);
125
126     // TODO: show progress in diff viewer
127     ProgressManager.getInstance().run(new Task.Backgroundable(project, VcsBundle.message("retrieving.annotations"), true,
128                                                               BackgroundFromStartOption.getInstance()) {
129       public void run(@NotNull ProgressIndicator indicator) {
130         loader.run();
131       }
132
133       @Override
134       public void onCancel() {
135         onSuccess();
136       }
137
138       @Override
139       public void onSuccess() {
140         markRunningProgress(viewer, side, false);
141
142         if (loader.getException() != null) {
143           AbstractVcsHelper.getInstance(myProject).showError(loader.getException(), VcsBundle.message("operation.name.annotate"));
144         }
145         if (loader.getResult() != null) {
146           if (viewer.isDisposed()) return;
147           annotator.showAnnotation(viewer, side, loader.getResult());
148         }
149       }
150     });
151   }
152
153   @Nullable
154   private static FileAnnotationLoader createAnnotationsLoader(@NotNull Project project, @NotNull DiffRequest request, @NotNull Side side) {
155     Change change = request.getUserData(ChangeDiffRequestProducer.CHANGE_KEY);
156     if (change != null) {
157       final ContentRevision revision = side.select(change.getBeforeRevision(), change.getAfterRevision());
158       if (revision == null) return null;
159       AbstractVcs vcs = ChangesUtil.getVcsForChange(change, project);
160       if (vcs == null) return null;
161
162       final AnnotationProvider annotationProvider = vcs.getAnnotationProvider();
163       if (annotationProvider == null) return null;
164
165       if (revision instanceof CurrentContentRevision) {
166         return new FileAnnotationLoader(vcs) {
167           @Override
168           public FileAnnotation compute() throws VcsException {
169             final VirtualFile file = ((CurrentContentRevision)revision).getVirtualFile();
170             if (file == null) throw new VcsException("Failed to annotate: file not found");
171             return annotationProvider.annotate(file);
172           }
173         };
174       }
175       else {
176         if (!(annotationProvider instanceof AnnotationProviderEx)) return null;
177         return new FileAnnotationLoader(vcs) {
178           @Override
179           public FileAnnotation compute() throws VcsException {
180             return ((AnnotationProviderEx)annotationProvider).annotate(revision.getFile(), revision.getRevisionNumber());
181           }
182         };
183       }
184     }
185
186     if (request instanceof ContentDiffRequest) {
187       ContentDiffRequest requestEx = (ContentDiffRequest)request;
188       if (requestEx.getContents().size() != 2) return null;
189       DiffContent content = side.select(requestEx.getContents());
190       if (content instanceof FileContent) {
191         final VirtualFile file = ((FileContent)content).getFile();
192         AbstractVcs vcs = VcsUtil.getVcsFor(project, file);
193         if (vcs == null) return null;
194
195         final AnnotationProvider annotationProvider = vcs.getAnnotationProvider();
196         if (annotationProvider == null) return null;
197
198         return new FileAnnotationLoader(vcs) {
199           @Override
200           public FileAnnotation compute() throws VcsException {
201             return annotationProvider.annotate(file);
202           }
203         };
204       }
205     }
206
207     return null;
208   }
209
210   private static class TwosideAnnotator extends ViewerAnnotator<TwosideTextDiffViewer> {
211     @Override
212     @NotNull
213     public Class<TwosideTextDiffViewer> getViewerClass() {
214       return TwosideTextDiffViewer.class;
215     }
216
217     @Override
218     @Nullable
219     public Side getCurrentSide(@NotNull TwosideTextDiffViewer viewer, @NotNull Editor editor) {
220       Side side = null; // we can't just use getCurrentSide() here, popup can be called on unfocused editor
221       if (viewer.getEditor(Side.LEFT) == editor) side = Side.LEFT;
222       if (viewer.getEditor(Side.RIGHT) == editor) side = Side.RIGHT;
223       return side;
224     }
225
226     @Override
227     public boolean isAnnotationShown(@NotNull TwosideTextDiffViewer viewer, @NotNull Side side) {
228       return viewer.getEditor(side).getGutter().isAnnotationsShown();
229     }
230
231     @Override
232     public void showAnnotation(@NotNull TwosideTextDiffViewer viewer, @NotNull Side side, @NotNull AnnotationData data) {
233       AnnotateToggleAction.doAnnotate(viewer.getEditor(side), viewer.getProject(), null, data.annotation, data.vcs, null);
234     }
235   }
236
237   private static class OnesideAnnotator extends ViewerAnnotator<OnesideTextDiffViewer> {
238     @Override
239     @NotNull
240     public Class<OnesideTextDiffViewer> getViewerClass() {
241       return OnesideTextDiffViewer.class;
242     }
243
244     @Override
245     @Nullable
246     public Side getCurrentSide(@NotNull OnesideTextDiffViewer viewer, @NotNull Editor editor) {
247       if (viewer.getEditor() != editor) return null;
248       return viewer.getSide();
249     }
250
251     @Override
252     public boolean isAnnotationShown(@NotNull OnesideTextDiffViewer viewer, @NotNull Side side) {
253       if (side != viewer.getSide()) return false;
254       return viewer.getEditor().getGutter().isAnnotationsShown();
255     }
256
257     @Override
258     public void showAnnotation(@NotNull OnesideTextDiffViewer viewer, @NotNull Side side, @NotNull AnnotationData data) {
259       if (side != viewer.getSide()) return;
260       AnnotateToggleAction.doAnnotate(viewer.getEditor(), viewer.getProject(), null, data.annotation, data.vcs, null);
261     }
262   }
263
264   private static class UnifiedAnnotator extends ViewerAnnotator<UnifiedDiffViewer> {
265     @Override
266     @NotNull
267     public Class<UnifiedDiffViewer> getViewerClass() {
268       return UnifiedDiffViewer.class;
269     }
270
271     @Override
272     @Nullable
273     public Side getCurrentSide(@NotNull UnifiedDiffViewer viewer, @NotNull Editor editor) {
274       if (viewer.getEditor() != editor) return null;
275       return viewer.getMasterSide();
276     }
277
278     @Override
279     public boolean isAnnotationShown(@NotNull UnifiedDiffViewer viewer, @NotNull Side side) {
280       if (side != viewer.getMasterSide()) return false;
281       return viewer.getEditor().getGutter().isAnnotationsShown();
282     }
283
284     @Override
285     public void showAnnotation(@NotNull UnifiedDiffViewer viewer, @NotNull Side side, @NotNull AnnotationData data) {
286       if (side != viewer.getMasterSide()) return;
287       UnifiedUpToDateLineNumberProvider lineNumberProvider = new UnifiedUpToDateLineNumberProvider(viewer, side);
288       AnnotateToggleAction.doAnnotate(viewer.getEditor(), viewer.getProject(), null, data.annotation, data.vcs, lineNumberProvider);
289     }
290   }
291
292   private static class UnifiedUpToDateLineNumberProvider implements UpToDateLineNumberProvider {
293     @NotNull private final UnifiedDiffViewer myViewer;
294     @NotNull private final Side mySide;
295     @NotNull private final UpToDateLineNumberProvider myLocalChangesProvider;
296
297     public UnifiedUpToDateLineNumberProvider(@NotNull UnifiedDiffViewer viewer, @NotNull Side side) {
298       myViewer = viewer;
299       mySide = side;
300       myLocalChangesProvider = new UpToDateLineNumberProviderImpl(myViewer.getDocument(mySide), viewer.getProject());
301     }
302
303     @Override
304     public int getLineNumber(int currentNumber) {
305       int number = myViewer.transferLineFromOnesideStrict(mySide, currentNumber);
306       return number != -1 ? myLocalChangesProvider.getLineNumber(number) : -1;
307     }
308
309     @Override
310     public boolean isLineChanged(int currentNumber) {
311       return getLineNumber(currentNumber) == -1;
312     }
313
314     @Override
315     public boolean isRangeChanged(int start, int end) {
316       int line1 = myViewer.transferLineFromOnesideStrict(mySide, start);
317       int line2 = myViewer.transferLineFromOnesideStrict(mySide, end);
318       if (line2 - line1 != end - start) return true;
319
320       for (int i = start; i <= end; i++) {
321         if (isLineChanged(i)) return true; // TODO: a single request to LineNumberConvertor
322       }
323       return myLocalChangesProvider.isRangeChanged(line1, line2);
324     }
325   }
326
327   private static abstract class ViewerAnnotator<T extends DiffViewerBase> {
328     @NotNull
329     public abstract Class<T> getViewerClass();
330
331     @Nullable
332     public abstract Side getCurrentSide(@NotNull T viewer, @NotNull Editor editor);
333
334     public abstract boolean isAnnotationShown(@NotNull T viewer, @NotNull Side side);
335
336     public abstract void showAnnotation(@NotNull T viewer, @NotNull Side side, @NotNull AnnotationData data);
337   }
338
339   private abstract static class FileAnnotationLoader {
340     @NotNull private final AbstractVcs myVcs;
341
342     private VcsException myException;
343     private FileAnnotation myResult;
344
345     public FileAnnotationLoader(@NotNull AbstractVcs vcs) {
346       myVcs = vcs;
347     }
348
349     public VcsException getException() {
350       return myException;
351     }
352
353     public AnnotationData getResult() {
354       return new AnnotationData(myVcs, myResult);
355     }
356
357     public void run() {
358       try {
359         myResult = compute();
360       }
361       catch (VcsException e) {
362         myException = e;
363       }
364     }
365
366     protected abstract FileAnnotation compute() throws VcsException;
367   }
368
369   private static class AnnotationData {
370     @NotNull public final AbstractVcs vcs;
371     @NotNull public final FileAnnotation annotation;
372
373     public AnnotationData(@NotNull AbstractVcs vcs, @NotNull FileAnnotation annotation) {
374       this.vcs = vcs;
375       this.annotation = annotation;
376     }
377   }
378
379   private static boolean checkRunningProgress(@NotNull DiffViewerBase viewer, @NotNull Side side) {
380     final ProjectLevelVcsManagerImpl plVcsManager = (ProjectLevelVcsManagerImpl)ProjectLevelVcsManager.getInstance(viewer.getProject());
381     final BackgroundableActionEnabledHandler handler = plVcsManager.getBackgroundableActionHandler(VcsBackgroundableActions.ANNOTATE);
382     return handler.isInProgress(key(viewer, side));
383   }
384
385   private static void markRunningProgress(@NotNull DiffViewerBase viewer, @NotNull Side side, boolean running) {
386     final ProjectLevelVcsManagerImpl plVcsManager = (ProjectLevelVcsManagerImpl)ProjectLevelVcsManager.getInstance(viewer.getProject());
387     final BackgroundableActionEnabledHandler handler = plVcsManager.getBackgroundableActionHandler(VcsBackgroundableActions.ANNOTATE);
388     if (running) {
389       handler.register(key(viewer, side));
390     }
391     else {
392       handler.completed(key(viewer, side));
393     }
394   }
395
396   @NotNull
397   private static Object key(@NotNull DiffViewer viewer, @NotNull Side side) {
398     return Pair.create(viewer, side);
399   }
400 }