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