2 * Copyright 2000-2015 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.intellij.openapi.vcs.actions;
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;
49 import java.util.ArrayList;
50 import java.util.HashMap;
51 import java.util.List;
55 * @author Konstantin Bulenkov
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");
63 public void update(@NotNull AnActionEvent e) {
65 Provider provider = getProvider(e);
66 e.getPresentation().setEnabled(provider != null && !provider.isSuspended(e));
70 public boolean isSelected(AnActionEvent e) {
71 Provider provider = getProvider(e);
72 return provider != null && provider.isAnnotated(e);
76 public void setSelected(AnActionEvent e, boolean selected) {
77 Provider provider = getProvider(e);
78 if (provider != null) provider.perform(e, selected);
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);
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() {
97 public void dispose() {
98 fileAnnotation.dispose();
102 if (fileAnnotation.getFile() != null && fileAnnotation.getFile().isInLocalFileSystem()) {
103 VcsAnnotationLocalChangesListener changesListener = ProjectLevelVcsManager.getInstance(project).getAnnotationLocalChangesListener();
105 changesListener.registerAnnotation(fileAnnotation.getFile(), fileAnnotation);
106 Disposer.register(disposable, new Disposable() {
108 public void dispose() {
109 changesListener.unregisterAnnotation(fileAnnotation.getFile(), fileAnnotation);
114 editor.getGutter().closeAllAnnotations();
116 fileAnnotation.setCloser(() -> {
117 UIUtil.invokeLaterIfNeeded(() -> {
118 if (project.isDisposed()) return;
119 editor.getGutter().closeAllAnnotations();
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);
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);
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));
141 presentation.addAction(new CopyRevisionNumberFromAnnotateAction(fileAnnotation));
142 presentation.addAction(Separator.getInstance());
144 final Couple<Map<VcsRevisionNumber, Color>> bgColorMap = computeBgColors(fileAnnotation, editor);
145 final Map<VcsRevisionNumber, Integer> historyIds = computeLineNumbers(fileAnnotation);
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);
155 SwitchAnnotationSourceAction switchAction = new SwitchAnnotationSourceAction(switcher, editorGutter);
156 presentation.addAction(switchAction);
157 switchAction.addSourceSwitchListener(currentRevisionGutter);
158 switchAction.addSourceSwitchListener(mergeSourceGutter);
160 currentRevisionGutter.consume(switcher.getDefaultSource());
161 mergeSourceGutter.consume(switcher.getDefaultSource());
163 gutters.add(currentRevisionGutter);
164 gutters.add(mergeSourceGutter);
167 final LineAnnotationAspect[] aspects = fileAnnotation.getAspects();
168 for (LineAnnotationAspect aspect : aspects) {
169 gutters.add(new AspectAnnotationFieldGutter(fileAnnotation, aspect, presentation, bgColorMap));
173 if (historyIds != null) {
174 gutters.add(new HistoryIdColumn(fileAnnotation, presentation, bgColorMap, historyIds));
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));
181 presentation.addAction(new AnnotateCurrentRevisionAction(fileAnnotation, vcs));
182 presentation.addAction(new AnnotatePreviousRevisionAction(fileAnnotation, vcs));
183 addActionsFromExtensions(presentation, fileAnnotation);
185 for (AnnotationFieldGutter gutter : gutters) {
186 final AnnotationGutterLineConvertorProxy proxy = new AnnotationGutterLineConvertorProxy(getUpToDateLineNumber, gutter);
187 if (gutter.isGutterAction()) {
188 editor.getGutter().registerTextAnnotation(proxy, proxy);
191 editor.getGutter().registerTextAnnotation(proxy);
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());
201 for (AnnotationGutterActionProvider provider : extensions) {
202 presentation.addAction(provider.createAction(fileAnnotation));
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();
216 numbers.put(number, size - i);
219 return numbers.size() < 2 ? null : numbers;
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<>();
227 EditorColorsScheme colorScheme = editor.getColorsScheme();
228 AnnotationsSettings settings = AnnotationsSettings.getInstance();
229 List<Color> authorsColorPalette = settings.getAuthorsColors(colorScheme);
230 List<Color> orderedColorPalette = settings.getOrderedColors(colorScheme);
232 FileAnnotation.AuthorsMappingProvider authorsMappingProvider = fileAnnotation.getAuthorsMappingProvider();
233 if (authorsMappingProvider != null) {
234 Map<VcsRevisionNumber, String> authorsMap = authorsMappingProvider.getAuthors();
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);
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);
251 FileAnnotation.RevisionsOrderProvider revisionsOrderProvider = fileAnnotation.getRevisionsOrderProvider();
252 if (revisionsOrderProvider != null) {
253 List<List<VcsRevisionNumber>> orderedRevisions = revisionsOrderProvider.getOrderedRevisions();
255 int revisionsCount = orderedRevisions.size();
256 for (int index = 0; index < revisionsCount; index++) {
257 Color color = orderedColorPalette.get(orderedColorPalette.size() * index / revisionsCount);
259 for (VcsRevisionNumber number : orderedRevisions.get(index)) {
260 commitOrderColors.put(number, color);
265 return Couple.of(commitOrderColors.size() > 1 ? commitOrderColors : null,
266 commitAuthorColors.size() > 1 ? commitAuthorColors : null);
270 private static Provider getProvider(AnActionEvent e) {
271 for (Provider provider : EP_NAME.getExtensions()) {
272 if (provider.isEnabled(e)) return provider;
277 public interface Provider {
278 boolean isEnabled(AnActionEvent e);
280 boolean isSuspended(AnActionEvent e);
282 boolean isAnnotated(AnActionEvent e);
284 void perform(AnActionEvent e, boolean selected);