Merge branch 'db/javac-ast'
[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.editor.Editor;
22 import com.intellij.openapi.editor.colors.EditorColorsScheme;
23 import com.intellij.openapi.editor.ex.EditorGutterComponentEx;
24 import com.intellij.openapi.extensions.ExtensionPointName;
25 import com.intellij.openapi.localVcs.UpToDateLineNumberProvider;
26 import com.intellij.openapi.project.DumbAware;
27 import com.intellij.openapi.project.Project;
28 import com.intellij.openapi.util.Comparing;
29 import com.intellij.openapi.util.Couple;
30 import com.intellij.openapi.vcs.AbstractVcs;
31 import com.intellij.openapi.vcs.ProjectLevelVcsManager;
32 import com.intellij.openapi.vcs.annotate.AnnotationGutterActionProvider;
33 import com.intellij.openapi.vcs.annotate.AnnotationSourceSwitcher;
34 import com.intellij.openapi.vcs.annotate.FileAnnotation;
35 import com.intellij.openapi.vcs.annotate.LineAnnotationAspect;
36 import com.intellij.openapi.vcs.history.VcsFileRevision;
37 import com.intellij.openapi.vcs.history.VcsRevisionNumber;
38 import com.intellij.openapi.vcs.impl.UpToDateLineNumberProviderImpl;
39 import com.intellij.openapi.vfs.VirtualFile;
40 import com.intellij.util.containers.ContainerUtil;
41 import com.intellij.util.ui.UIUtil;
42 import org.jetbrains.annotations.NotNull;
43 import org.jetbrains.annotations.Nullable;
44
45 import java.awt.*;
46 import java.util.ArrayList;
47 import java.util.HashMap;
48 import java.util.List;
49 import java.util.Map;
50
51 /**
52  * @author Konstantin Bulenkov
53  * @author: lesya
54  */
55 public class AnnotateToggleAction extends ToggleAction implements DumbAware {
56   public static final ExtensionPointName<Provider> EP_NAME =
57     ExtensionPointName.create("com.intellij.openapi.vcs.actions.AnnotateToggleAction.Provider");
58
59   @Override
60   public void update(@NotNull AnActionEvent e) {
61     super.update(e);
62     Provider provider = getProvider(e);
63     e.getPresentation().setEnabled(provider != null && !provider.isSuspended(e));
64   }
65
66   @Override
67   public boolean isSelected(AnActionEvent e) {
68     Provider provider = getProvider(e);
69     return provider != null && provider.isAnnotated(e);
70   }
71
72   @Override
73   public void setSelected(AnActionEvent e, boolean selected) {
74     Provider provider = getProvider(e);
75     if (provider != null) provider.perform(e, selected);
76   }
77
78   public static void doAnnotate(@NotNull final Editor editor,
79                                 @NotNull final Project project,
80                                 @Nullable final VirtualFile currentFile,
81                                 @NotNull final FileAnnotation fileAnnotation,
82                                 @NotNull final AbstractVcs vcs) {
83     doAnnotate(editor, project, currentFile, fileAnnotation, vcs, null);
84   }
85
86   public static void doAnnotate(@NotNull final Editor editor,
87                                 @NotNull final Project project,
88                                 @Nullable final VirtualFile currentFile,
89                                 @NotNull final FileAnnotation fileAnnotation,
90                                 @NotNull final AbstractVcs vcs,
91                                 @Nullable final UpToDateLineNumberProvider upToDateLineNumberProvider) {
92     if (fileAnnotation.getFile() != null && fileAnnotation.getFile().isInLocalFileSystem()) {
93       ProjectLevelVcsManager.getInstance(project).getAnnotationLocalChangesListener().registerAnnotation(fileAnnotation.getFile(), fileAnnotation);
94     }
95
96     editor.getGutter().closeAllAnnotations();
97
98     fileAnnotation.setCloser(() -> {
99       UIUtil.invokeLaterIfNeeded(() -> {
100         if (project.isDisposed()) return;
101         editor.getGutter().closeAllAnnotations();
102       });
103     });
104
105     fileAnnotation.setReloader(newFileAnnotation -> {
106       if (editor.getGutter().isAnnotationsShown()) {
107         assert Comparing.equal(fileAnnotation.getFile(), newFileAnnotation.getFile());
108         doAnnotate(editor, project, currentFile, newFileAnnotation, vcs, upToDateLineNumberProvider);
109       }
110     });
111
112     final EditorGutterComponentEx editorGutter = (EditorGutterComponentEx)editor.getGutter();
113     final List<AnnotationFieldGutter> gutters = new ArrayList<>();
114     final AnnotationSourceSwitcher switcher = fileAnnotation.getAnnotationSourceSwitcher();
115     UpToDateLineNumberProvider getUpToDateLineNumber = upToDateLineNumberProvider != null ?
116                                                        upToDateLineNumberProvider :
117                                                        new UpToDateLineNumberProviderImpl(editor.getDocument(), project);
118
119     final AnnotationPresentation presentation = new AnnotationPresentation(fileAnnotation, getUpToDateLineNumber, switcher);
120     if (currentFile != null && vcs.getCommittedChangesProvider() != null) {
121       presentation.addAction(new ShowDiffFromAnnotation(fileAnnotation, vcs, currentFile));
122     }
123     presentation.addAction(new CopyRevisionNumberFromAnnotateAction(fileAnnotation));
124     presentation.addAction(Separator.getInstance());
125
126     final Couple<Map<VcsRevisionNumber, Color>> bgColorMap = computeBgColors(fileAnnotation, editor);
127     final Map<VcsRevisionNumber, Integer> historyIds = computeLineNumbers(fileAnnotation);
128
129     if (switcher != null) {
130       switcher.switchTo(switcher.getDefaultSource());
131       final LineAnnotationAspect revisionAspect = switcher.getRevisionAspect();
132       final CurrentRevisionAnnotationFieldGutter currentRevisionGutter =
133         new CurrentRevisionAnnotationFieldGutter(fileAnnotation, revisionAspect, presentation, bgColorMap);
134       final MergeSourceAvailableMarkerGutter mergeSourceGutter =
135         new MergeSourceAvailableMarkerGutter(fileAnnotation, null, presentation, bgColorMap);
136
137       SwitchAnnotationSourceAction switchAction = new SwitchAnnotationSourceAction(switcher, editorGutter);
138       presentation.addAction(switchAction);
139       switchAction.addSourceSwitchListener(currentRevisionGutter);
140       switchAction.addSourceSwitchListener(mergeSourceGutter);
141
142       currentRevisionGutter.consume(switcher.getDefaultSource());
143       mergeSourceGutter.consume(switcher.getDefaultSource());
144
145       gutters.add(currentRevisionGutter);
146       gutters.add(mergeSourceGutter);
147     }
148
149     final LineAnnotationAspect[] aspects = fileAnnotation.getAspects();
150     for (LineAnnotationAspect aspect : aspects) {
151       gutters.add(new AnnotationFieldGutter(fileAnnotation, aspect, presentation, bgColorMap));
152     }
153
154
155     if (historyIds != null) {
156       gutters.add(new HistoryIdColumn(fileAnnotation, presentation, bgColorMap, historyIds));
157     }
158     gutters.add(new HighlightedAdditionalColumn(fileAnnotation, null, presentation, bgColorMap));
159     final AnnotateActionGroup actionGroup = new AnnotateActionGroup(gutters, editorGutter, bgColorMap);
160     presentation.addAction(actionGroup, 1);
161     gutters.add(new ExtraFieldGutter(fileAnnotation, presentation, bgColorMap, actionGroup));
162
163     presentation.addAction(new AnnotateCurrentRevisionAction(fileAnnotation, vcs));
164     presentation.addAction(new AnnotatePreviousRevisionAction(fileAnnotation, vcs));
165     addActionsFromExtensions(presentation, fileAnnotation);
166
167     for (AnnotationFieldGutter gutter : gutters) {
168       final AnnotationGutterLineConvertorProxy proxy = new AnnotationGutterLineConvertorProxy(getUpToDateLineNumber, gutter);
169       if (gutter.isGutterAction()) {
170         editor.getGutter().registerTextAnnotation(proxy, proxy);
171       }
172       else {
173         editor.getGutter().registerTextAnnotation(proxy);
174       }
175     }
176   }
177
178   private static void addActionsFromExtensions(@NotNull AnnotationPresentation presentation, @NotNull FileAnnotation fileAnnotation) {
179     AnnotationGutterActionProvider[] extensions = AnnotationGutterActionProvider.EP_NAME.getExtensions();
180     if (extensions.length > 0) {
181       presentation.addAction(new Separator());
182     }
183     for (AnnotationGutterActionProvider provider : extensions) {
184       presentation.addAction(provider.createAction(fileAnnotation));
185     }
186   }
187
188   @Nullable
189   private static Map<VcsRevisionNumber, Integer> computeLineNumbers(@NotNull FileAnnotation fileAnnotation) {
190     final Map<VcsRevisionNumber, Integer> numbers = new HashMap<>();
191     final List<VcsFileRevision> fileRevisionList = fileAnnotation.getRevisions();
192     if (fileRevisionList != null) {
193       int size = fileRevisionList.size();
194       for (int i = 0; i < size; i++) {
195         VcsFileRevision revision = fileRevisionList.get(i);
196         final VcsRevisionNumber number = revision.getRevisionNumber();
197
198         numbers.put(number, size - i);
199       }
200     }
201     return numbers.size() < 2 ? null : numbers;
202   }
203
204   @Nullable
205   private static Couple<Map<VcsRevisionNumber, Color>> computeBgColors(@NotNull FileAnnotation fileAnnotation, @NotNull Editor editor) {
206     Map<VcsRevisionNumber, Color> commitOrderColors = new HashMap<>();
207     Map<VcsRevisionNumber, Color> commitAuthorColors = new HashMap<>();
208
209     EditorColorsScheme colorScheme = editor.getColorsScheme();
210     AnnotationsSettings settings = AnnotationsSettings.getInstance();
211     List<Color> authorsColorPalette = settings.getAuthorsColors(colorScheme);
212     List<Color> orderedColorPalette = settings.getOrderedColors(colorScheme);
213
214     FileAnnotation.AuthorsMappingProvider authorsMappingProvider = fileAnnotation.getAuthorsMappingProvider();
215     if (authorsMappingProvider != null) {
216       Map<VcsRevisionNumber, String> authorsMap = authorsMappingProvider.getAuthors();
217
218       Map<String, Color> authorColors = new HashMap<>();
219       for (String author : ContainerUtil.sorted(authorsMap.values(), Comparing::compare)) {
220         int index = authorColors.size();
221         Color color = authorsColorPalette.get(index % authorsColorPalette.size());
222         authorColors.put(author, color);
223       }
224
225       for (Map.Entry<VcsRevisionNumber, String> entry : authorsMap.entrySet()) {
226         VcsRevisionNumber revision = entry.getKey();
227         String author = entry.getValue();
228         Color color = authorColors.get(author);
229         commitAuthorColors.put(revision, color);
230       }
231     }
232
233     FileAnnotation.RevisionsOrderProvider revisionsOrderProvider = fileAnnotation.getRevisionsOrderProvider();
234     if (revisionsOrderProvider != null) {
235       List<List<VcsRevisionNumber>> orderedRevisions = revisionsOrderProvider.getOrderedRevisions();
236
237       int revisionsCount = orderedRevisions.size();
238       for (int index = 0; index < revisionsCount; index++) {
239         Color color = orderedColorPalette.get(orderedColorPalette.size() * index / revisionsCount);
240
241         for (VcsRevisionNumber number : orderedRevisions.get(index)) {
242           commitOrderColors.put(number, color);
243         }
244       }
245     }
246
247     return Couple.of(commitOrderColors.size() > 1 ? commitOrderColors : null,
248                      commitAuthorColors.size() > 1 ? commitAuthorColors : null);
249   }
250
251   @Nullable
252   private static Provider getProvider(AnActionEvent e) {
253     for (Provider provider : EP_NAME.getExtensions()) {
254       if (provider.isEnabled(e)) return provider;
255     }
256     return null;
257   }
258
259   public interface Provider {
260     boolean isEnabled(AnActionEvent e);
261
262     boolean isSuspended(AnActionEvent e);
263
264     boolean isAnnotated(AnActionEvent e);
265
266     void perform(AnActionEvent e, boolean selected);
267   }
268 }