ca182166e500477ae94afd6e749c2cb9283d7908
[idea/community.git] / platform / vcs-impl / src / com / intellij / openapi / vcs / actions / AnnotateToggleAction.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.openapi.actionSystem.AnActionEvent;
19 import com.intellij.openapi.actionSystem.Separator;
20 import com.intellij.openapi.actionSystem.ToggleAction;
21 import com.intellij.openapi.diagnostic.Logger;
22 import com.intellij.openapi.editor.Editor;
23 import com.intellij.openapi.editor.ex.EditorGutterComponentEx;
24 import com.intellij.openapi.fileEditor.FileDocumentManager;
25 import com.intellij.openapi.fileEditor.FileEditor;
26 import com.intellij.openapi.fileEditor.FileEditorManager;
27 import com.intellij.openapi.fileEditor.TextEditor;
28 import com.intellij.openapi.localVcs.UpToDateLineNumberProvider;
29 import com.intellij.openapi.progress.ProgressIndicator;
30 import com.intellij.openapi.progress.ProgressManager;
31 import com.intellij.openapi.progress.Task;
32 import com.intellij.openapi.project.DumbAware;
33 import com.intellij.openapi.project.Project;
34 import com.intellij.openapi.util.Couple;
35 import com.intellij.openapi.util.Ref;
36 import com.intellij.openapi.util.registry.Registry;
37 import com.intellij.openapi.vcs.*;
38 import com.intellij.openapi.vcs.annotate.*;
39 import com.intellij.openapi.vcs.changes.BackgroundFromStartOption;
40 import com.intellij.openapi.vcs.history.VcsFileRevision;
41 import com.intellij.openapi.vcs.history.VcsRevisionNumber;
42 import com.intellij.openapi.vcs.impl.BackgroundableActionEnabledHandler;
43 import com.intellij.openapi.vcs.impl.ProjectLevelVcsManagerImpl;
44 import com.intellij.openapi.vcs.impl.UpToDateLineNumberProviderImpl;
45 import com.intellij.openapi.vcs.impl.VcsBackgroundableActions;
46 import com.intellij.openapi.vfs.VirtualFile;
47 import com.intellij.util.ui.UIUtil;
48 import org.jetbrains.annotations.NotNull;
49 import org.jetbrains.annotations.Nullable;
50
51 import java.awt.*;
52 import java.util.*;
53 import java.util.List;
54
55 /**
56  * @author Konstantin Bulenkov
57  * @author: lesya
58  */
59 public class AnnotateToggleAction extends ToggleAction implements DumbAware, AnnotationColors {
60   private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.actions.AnnotateToggleAction");
61
62   @Override
63   public void update(@NotNull AnActionEvent e) {
64     super.update(e);
65     final boolean enabled = isEnabled(VcsContextFactory.SERVICE.getInstance().createContextOn(e));
66     e.getPresentation().setEnabled(enabled);
67   }
68
69   private static boolean isEnabled(final VcsContext context) {
70     VirtualFile[] selectedFiles = context.getSelectedFiles();
71     if (selectedFiles.length != 1) {
72       return false;
73     }
74     VirtualFile file = selectedFiles[0];
75     if (file.isDirectory()) return false;
76     Project project = context.getProject();
77     if (project == null || project.isDisposed()) return false;
78
79     final ProjectLevelVcsManager plVcsManager = ProjectLevelVcsManager.getInstance(project);
80     final BackgroundableActionEnabledHandler handler = ((ProjectLevelVcsManagerImpl)plVcsManager)
81       .getBackgroundableActionHandler(VcsBackgroundableActions.ANNOTATE);
82     if (handler.isInProgress(file.getPath())) return false;
83
84     final AbstractVcs vcs = plVcsManager.getVcsFor(file);
85     if (vcs == null) return false;
86     final AnnotationProvider annotationProvider = vcs.getAnnotationProvider();
87     if (annotationProvider == null) return false;
88     final FileStatus fileStatus = FileStatusManager.getInstance(project).getStatus(file);
89     if (fileStatus == FileStatus.UNKNOWN || fileStatus == FileStatus.ADDED || fileStatus == FileStatus.IGNORED) {
90       return false;
91     }
92     return hasTextEditor(file);
93   }
94
95   private static boolean hasTextEditor(@NotNull VirtualFile selectedFile) {
96     return !selectedFile.getFileType().isBinary();
97   }
98
99   @Override
100   public boolean isSelected(AnActionEvent e) {
101     VcsContext context = VcsContextFactory.SERVICE.getInstance().createContextOn(e);
102     Editor editor = context.getEditor();
103     if (editor != null) {
104       return isAnnotated(editor);
105     }
106     VirtualFile selectedFile = context.getSelectedFile();
107     if (selectedFile == null) {
108       return false;
109     }
110
111     Project project = context.getProject();
112     if (project == null) return false;
113
114     for (FileEditor fileEditor : FileEditorManager.getInstance(project).getEditors(selectedFile)) {
115       if (fileEditor instanceof TextEditor) {
116         if (isAnnotated(((TextEditor)fileEditor).getEditor())) {
117           return true;
118         }
119       }
120     }
121     return false;
122   }
123
124   private static boolean isAnnotated(@NotNull Editor editor) {
125     return editor.getGutter().isAnnotationsShown();
126   }
127
128   @Override
129   public void setSelected(AnActionEvent e, boolean selected) {
130     final VcsContext context = VcsContextFactory.SERVICE.getInstance().createContextOn(e);
131     Editor editor = context.getEditor();
132     VirtualFile selectedFile = context.getSelectedFile();
133     if (selectedFile == null) return;
134
135     Project project = context.getProject();
136     if (project == null) return;
137     if (!selected) {
138       for (FileEditor fileEditor : FileEditorManager.getInstance(project).getEditors(selectedFile)) {
139         if (fileEditor instanceof TextEditor) {
140           ((TextEditor)fileEditor).getEditor().getGutter().closeAllAnnotations();
141         }
142       }
143     }
144     else {
145       if (editor == null) {
146         FileEditor[] fileEditors = FileEditorManager.getInstance(project).openFile(selectedFile, false);
147         for (FileEditor fileEditor : fileEditors) {
148           if (fileEditor instanceof TextEditor) {
149             editor = ((TextEditor)fileEditor).getEditor();
150           }
151         }
152       }
153       LOG.assertTrue(editor != null);
154       doAnnotate(editor, project);
155     }
156   }
157
158   private static void doAnnotate(final Editor editor, final Project project) {
159     final VirtualFile file = FileDocumentManager.getInstance().getFile(editor.getDocument());
160     if (project == null || file == null) {
161       return;
162     }
163     final ProjectLevelVcsManager plVcsManager = ProjectLevelVcsManager.getInstance(project);
164     final AbstractVcs vcs = plVcsManager.getVcsFor(file);
165
166     if (vcs == null) return;
167
168     final AnnotationProvider annotationProvider = vcs.getCachingAnnotationProvider();
169     assert annotationProvider != null;
170
171     final Ref<FileAnnotation> fileAnnotationRef = new Ref<FileAnnotation>();
172     final Ref<VcsException> exceptionRef = new Ref<VcsException>();
173
174     final BackgroundableActionEnabledHandler handler = ((ProjectLevelVcsManagerImpl)plVcsManager).getBackgroundableActionHandler(
175       VcsBackgroundableActions.ANNOTATE);
176     handler.register(file.getPath());
177
178     final Task.Backgroundable annotateTask = new Task.Backgroundable(project,
179                                                                      VcsBundle.message("retrieving.annotations"),
180                                                                      true,
181                                                                      BackgroundFromStartOption.getInstance()) {
182       @Override
183       public void run(final @NotNull ProgressIndicator indicator) {
184         try {
185           fileAnnotationRef.set(annotationProvider.annotate(file));
186         }
187         catch (VcsException e) {
188           exceptionRef.set(e);
189         }
190         catch (Throwable t) {
191           exceptionRef.set(new VcsException(t));
192         }
193       }
194
195       @Override
196       public void onCancel() {
197         onSuccess();
198       }
199
200       @Override
201       public void onSuccess() {
202         handler.completed(file.getPath());
203
204         if (!exceptionRef.isNull()) {
205           LOG.warn(exceptionRef.get());
206           AbstractVcsHelper.getInstance(project).showErrors(Collections.singletonList(exceptionRef.get()), VcsBundle.message("message.title.annotate"));
207         }
208
209         if (!fileAnnotationRef.isNull()) {
210           doAnnotate(editor, project, file, fileAnnotationRef.get(), vcs, true);
211         }
212       }
213     };
214     ProgressManager.getInstance().run(annotateTask);
215   }
216
217   public static void doAnnotate(@NotNull final Editor editor,
218                                 @NotNull final Project project,
219                                 @NotNull final VirtualFile currentFile,
220                                 @NotNull final FileAnnotation fileAnnotation,
221                                 @NotNull final AbstractVcs vcs,
222                                 final boolean onCurrentRevision) {
223     if (onCurrentRevision) {
224       ProjectLevelVcsManager.getInstance(project).getAnnotationLocalChangesListener().registerAnnotation(fileAnnotation.getFile(), fileAnnotation);
225     }
226     doAnnotate(editor, project, currentFile, fileAnnotation, vcs, null);
227   }
228
229   public static void doAnnotate(@NotNull final Editor editor,
230                                 @NotNull final Project project,
231                                 @Nullable final VirtualFile currentFile,
232                                 @NotNull final FileAnnotation fileAnnotation,
233                                 @NotNull final AbstractVcs vcs,
234                                 @Nullable UpToDateLineNumberProvider getUpToDateLineNumber) {
235     editor.getGutter().closeAllAnnotations();
236
237     fileAnnotation.setCloser(new Runnable() {
238       @Override
239       public void run() {
240         if (project.isDisposed()) return;
241         UIUtil.invokeLaterIfNeeded(new Runnable() {
242           @Override
243           public void run() {
244             if (project.isDisposed()) return;
245             editor.getGutter().closeAllAnnotations();
246           }
247         });
248       }
249     });
250
251
252     final EditorGutterComponentEx editorGutter = (EditorGutterComponentEx)editor.getGutter();
253     final List<AnnotationFieldGutter> gutters = new ArrayList<AnnotationFieldGutter>();
254     final AnnotationSourceSwitcher switcher = fileAnnotation.getAnnotationSourceSwitcher();
255     if (getUpToDateLineNumber == null) getUpToDateLineNumber = new UpToDateLineNumberProviderImpl(editor.getDocument(), project);
256
257     final AnnotationPresentation presentation = new AnnotationPresentation(fileAnnotation, getUpToDateLineNumber, switcher);
258     if (currentFile != null && vcs.getCommittedChangesProvider() != null) {
259       presentation.addAction(new ShowDiffFromAnnotation(fileAnnotation, vcs, currentFile));
260     }
261     presentation.addAction(new CopyRevisionNumberFromAnnotateAction(fileAnnotation));
262     presentation.addAction(Separator.getInstance());
263
264     final Couple<Map<VcsRevisionNumber, Color>> bgColorMap =
265       Registry.is("vcs.show.colored.annotations") ? computeBgColors(fileAnnotation) : null;
266     final Map<VcsRevisionNumber, Integer> historyIds = Registry.is("vcs.show.history.numbers") ? computeLineNumbers(fileAnnotation) : null;
267
268     if (switcher != null) {
269       switcher.switchTo(switcher.getDefaultSource());
270       final LineAnnotationAspect revisionAspect = switcher.getRevisionAspect();
271       final CurrentRevisionAnnotationFieldGutter currentRevisionGutter =
272         new CurrentRevisionAnnotationFieldGutter(fileAnnotation, revisionAspect, presentation, bgColorMap);
273       final MergeSourceAvailableMarkerGutter mergeSourceGutter =
274         new MergeSourceAvailableMarkerGutter(fileAnnotation, null, presentation, bgColorMap);
275
276       presentation.addAction(new SwitchAnnotationSourceAction(switcher, editorGutter));
277       presentation.addSourceSwitchListener(currentRevisionGutter);
278       presentation.addSourceSwitchListener(mergeSourceGutter);
279
280       currentRevisionGutter.consume(switcher.getDefaultSource());
281       mergeSourceGutter.consume(switcher.getDefaultSource());
282
283       gutters.add(currentRevisionGutter);
284       gutters.add(mergeSourceGutter);
285     }
286
287     final LineAnnotationAspect[] aspects = fileAnnotation.getAspects();
288     for (LineAnnotationAspect aspect : aspects) {
289       gutters.add(new AnnotationFieldGutter(fileAnnotation, aspect, presentation, bgColorMap));
290     }
291
292
293     if (historyIds != null) {
294       gutters.add(new HistoryIdColumn(fileAnnotation, presentation, bgColorMap, historyIds));
295     }
296     gutters.add(new HighlightedAdditionalColumn(fileAnnotation, null, presentation, bgColorMap));
297     final AnnotateActionGroup actionGroup = new AnnotateActionGroup(gutters, editorGutter);
298     presentation.addAction(actionGroup, 1);
299     gutters.add(new ExtraFieldGutter(fileAnnotation, presentation, bgColorMap, actionGroup));
300
301     presentation.addAction(new AnnotateCurrentRevisionAction(fileAnnotation, vcs));
302     presentation.addAction(new AnnotatePreviousRevisionAction(fileAnnotation, vcs));
303     addActionsFromExtensions(presentation, fileAnnotation);
304
305     for (AnnotationFieldGutter gutter : gutters) {
306       final AnnotationGutterLineConvertorProxy proxy = new AnnotationGutterLineConvertorProxy(getUpToDateLineNumber, gutter);
307       if (gutter.isGutterAction()) {
308         editor.getGutter().registerTextAnnotation(proxy, proxy);
309       }
310       else {
311         editor.getGutter().registerTextAnnotation(proxy);
312       }
313     }
314   }
315
316   private static void addActionsFromExtensions(@NotNull AnnotationPresentation presentation, @NotNull FileAnnotation fileAnnotation) {
317     AnnotationGutterActionProvider[] extensions = AnnotationGutterActionProvider.EP_NAME.getExtensions();
318     if (extensions.length > 0) {
319       presentation.addAction(new Separator());
320     }
321     for (AnnotationGutterActionProvider provider : extensions) {
322       presentation.addAction(provider.createAction(fileAnnotation));
323     }
324   }
325
326   @Nullable
327   private static Map<VcsRevisionNumber, Integer> computeLineNumbers(@NotNull FileAnnotation fileAnnotation) {
328     final Map<VcsRevisionNumber, Integer> numbers = new HashMap<VcsRevisionNumber, Integer>();
329     final List<VcsFileRevision> fileRevisionList = fileAnnotation.getRevisions();
330     if (fileRevisionList != null) {
331       int size = fileRevisionList.size();
332       for (int i = 0; i < size; i++) {
333         VcsFileRevision revision = fileRevisionList.get(i);
334         final VcsRevisionNumber number = revision.getRevisionNumber();
335
336         numbers.put(number, size - i);
337       }
338     }
339     return numbers.size() < 2 ? null : numbers;
340   }
341
342   @NotNull
343   private static Couple<Map<VcsRevisionNumber, Color>> computeBgColors(@NotNull FileAnnotation fileAnnotation) {
344     final Map<VcsRevisionNumber, Color> commitOrderColors = new HashMap<VcsRevisionNumber, Color>();
345     final Map<VcsRevisionNumber, Color> commitAuthorColors = new HashMap<VcsRevisionNumber, Color>();
346     final Map<String, Color> authorColors = new HashMap<String, Color>();
347     final List<VcsFileRevision> fileRevisionList = fileAnnotation.getRevisions();
348     if (fileRevisionList != null) {
349       final int colorsCount = BG_COLORS.length;
350       final int revisionsCount = fileRevisionList.size();
351
352       for (int i = 0; i < fileRevisionList.size(); i++) {
353         VcsFileRevision revision = fileRevisionList.get(i);
354         final VcsRevisionNumber number = revision.getRevisionNumber();
355         final String author = revision.getAuthor();
356         if (number == null) continue;
357
358         if (!commitAuthorColors.containsKey(number)) {
359           if (author != null && !authorColors.containsKey(author)) {
360             final int index = authorColors.size();
361             Color color = BG_COLORS[index * BG_COLORS_PRIME % colorsCount];
362             authorColors.put(author, color);
363           }
364
365           commitAuthorColors.put(number, authorColors.get(author));
366         }
367         if (!commitOrderColors.containsKey(number)) {
368           Color color = BG_COLORS[colorsCount * i / revisionsCount];
369           commitOrderColors.put(number, color);
370         }
371       }
372     }
373     return Couple.of(commitOrderColors.size() > 1 ? commitOrderColors : null,
374                      commitAuthorColors.size() > 1 ? commitAuthorColors : null);
375   }
376 }