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.actionSystem.AnAction;
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.diagnostic.Logger;
23 import com.intellij.openapi.editor.Editor;
24 import com.intellij.openapi.editor.ex.EditorGutterComponentEx;
25 import com.intellij.openapi.fileEditor.FileDocumentManager;
26 import com.intellij.openapi.fileEditor.FileEditor;
27 import com.intellij.openapi.fileEditor.FileEditorManager;
28 import com.intellij.openapi.fileEditor.TextEditor;
29 import com.intellij.openapi.localVcs.UpToDateLineNumberProvider;
30 import com.intellij.openapi.progress.ProgressIndicator;
31 import com.intellij.openapi.progress.ProgressManager;
32 import com.intellij.openapi.progress.Task;
33 import com.intellij.openapi.project.DumbAware;
34 import com.intellij.openapi.project.Project;
35 import com.intellij.openapi.util.Couple;
36 import com.intellij.openapi.util.Ref;
37 import com.intellij.openapi.util.registry.Registry;
38 import com.intellij.openapi.vcs.*;
39 import com.intellij.openapi.vcs.annotate.*;
40 import com.intellij.openapi.vcs.changes.BackgroundFromStartOption;
41 import com.intellij.openapi.vcs.history.VcsFileRevision;
42 import com.intellij.openapi.vcs.history.VcsRevisionNumber;
43 import com.intellij.openapi.vcs.impl.BackgroundableActionEnabledHandler;
44 import com.intellij.openapi.vcs.impl.ProjectLevelVcsManagerImpl;
45 import com.intellij.openapi.vcs.impl.UpToDateLineNumberProviderImpl;
46 import com.intellij.openapi.vcs.impl.VcsBackgroundableActions;
47 import com.intellij.openapi.vfs.VirtualFile;
48 import com.intellij.util.ui.UIUtil;
49 import org.jetbrains.annotations.NotNull;
50 import org.jetbrains.annotations.Nullable;
54 import java.util.List;
57 * @author Konstantin Bulenkov
60 public class AnnotateToggleAction extends ToggleAction implements DumbAware, AnnotationColors {
61 private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.actions.AnnotateToggleAction");
64 public void update(@NotNull AnActionEvent e) {
66 final boolean enabled = isEnabled(VcsContextFactory.SERVICE.getInstance().createContextOn(e));
67 e.getPresentation().setEnabled(enabled);
70 private static boolean isEnabled(final VcsContext context) {
71 VirtualFile[] selectedFiles = context.getSelectedFiles();
72 if (selectedFiles.length != 1) {
75 VirtualFile file = selectedFiles[0];
76 if (file.isDirectory()) return false;
77 Project project = context.getProject();
78 if (project == null || project.isDisposed()) return false;
80 final ProjectLevelVcsManager plVcsManager = ProjectLevelVcsManager.getInstance(project);
81 final BackgroundableActionEnabledHandler handler = ((ProjectLevelVcsManagerImpl)plVcsManager)
82 .getBackgroundableActionHandler(VcsBackgroundableActions.ANNOTATE);
83 if (handler.isInProgress(file.getPath())) return false;
85 final AbstractVcs vcs = plVcsManager.getVcsFor(file);
86 if (vcs == null) return false;
87 final AnnotationProvider annotationProvider = vcs.getAnnotationProvider();
88 if (annotationProvider == null) return false;
89 final FileStatus fileStatus = FileStatusManager.getInstance(project).getStatus(file);
90 if (fileStatus == FileStatus.UNKNOWN || fileStatus == FileStatus.ADDED || fileStatus == FileStatus.IGNORED) {
93 return hasTextEditor(file);
96 private static boolean hasTextEditor(@NotNull VirtualFile selectedFile) {
97 return !selectedFile.getFileType().isBinary();
101 public boolean isSelected(AnActionEvent e) {
102 VcsContext context = VcsContextFactory.SERVICE.getInstance().createContextOn(e);
103 Editor editor = context.getEditor();
104 if (editor != null) {
105 return isAnnotated(editor);
107 VirtualFile selectedFile = context.getSelectedFile();
108 if (selectedFile == null) {
112 Project project = context.getProject();
113 if (project == null) return false;
115 for (FileEditor fileEditor : FileEditorManager.getInstance(project).getEditors(selectedFile)) {
116 if (fileEditor instanceof TextEditor) {
117 if (isAnnotated(((TextEditor)fileEditor).getEditor())) {
125 private static boolean isAnnotated(@NotNull Editor editor) {
126 return editor.getGutter().isAnnotationsShown();
130 public void setSelected(AnActionEvent e, boolean selected) {
131 final VcsContext context = VcsContextFactory.SERVICE.getInstance().createContextOn(e);
132 Editor editor = context.getEditor();
133 VirtualFile selectedFile = context.getSelectedFile();
134 if (selectedFile == null) return;
136 Project project = context.getProject();
137 if (project == null) return;
139 for (FileEditor fileEditor : FileEditorManager.getInstance(project).getEditors(selectedFile)) {
140 if (fileEditor instanceof TextEditor) {
141 ((TextEditor)fileEditor).getEditor().getGutter().closeAllAnnotations();
146 if (editor == null) {
147 FileEditor[] fileEditors = FileEditorManager.getInstance(project).openFile(selectedFile, false);
148 for (FileEditor fileEditor : fileEditors) {
149 if (fileEditor instanceof TextEditor) {
150 editor = ((TextEditor)fileEditor).getEditor();
154 LOG.assertTrue(editor != null);
155 doAnnotate(editor, project);
159 private static void doAnnotate(final Editor editor, final Project project) {
160 final VirtualFile file = FileDocumentManager.getInstance().getFile(editor.getDocument());
161 if (project == null || file == null) {
164 final ProjectLevelVcsManager plVcsManager = ProjectLevelVcsManager.getInstance(project);
165 final AbstractVcs vcs = plVcsManager.getVcsFor(file);
167 if (vcs == null) return;
169 final AnnotationProvider annotationProvider = vcs.getCachingAnnotationProvider();
170 assert annotationProvider != null;
172 final Ref<FileAnnotation> fileAnnotationRef = new Ref<FileAnnotation>();
173 final Ref<VcsException> exceptionRef = new Ref<VcsException>();
175 final BackgroundableActionEnabledHandler handler = ((ProjectLevelVcsManagerImpl)plVcsManager).getBackgroundableActionHandler(
176 VcsBackgroundableActions.ANNOTATE);
177 handler.register(file.getPath());
179 final Task.Backgroundable annotateTask = new Task.Backgroundable(project,
180 VcsBundle.message("retrieving.annotations"),
182 BackgroundFromStartOption.getInstance()) {
184 public void run(final @NotNull ProgressIndicator indicator) {
186 fileAnnotationRef.set(annotationProvider.annotate(file));
188 catch (VcsException e) {
191 catch (Throwable t) {
192 exceptionRef.set(new VcsException(t));
197 public void onCancel() {
202 public void onSuccess() {
203 handler.completed(file.getPath());
205 if (!exceptionRef.isNull()) {
206 LOG.warn(exceptionRef.get());
207 AbstractVcsHelper.getInstance(project).showErrors(Collections.singletonList(exceptionRef.get()), VcsBundle.message("message.title.annotate"));
210 if (!fileAnnotationRef.isNull()) {
211 doAnnotate(editor, project, file, fileAnnotationRef.get(), vcs, true);
215 ProgressManager.getInstance().run(annotateTask);
218 public static void doAnnotate(@NotNull final Editor editor,
219 @NotNull final Project project,
220 @NotNull final VirtualFile currentFile,
221 @NotNull final FileAnnotation fileAnnotation,
222 @NotNull final AbstractVcs vcs,
223 final boolean onCurrentRevision) {
224 editor.getGutter().closeAllAnnotations();
226 fileAnnotation.setCloser(new Runnable() {
229 if (project.isDisposed()) return;
230 UIUtil.invokeLaterIfNeeded(new Runnable() {
233 if (project.isDisposed()) return;
234 editor.getGutter().closeAllAnnotations();
239 if (onCurrentRevision) {
240 ProjectLevelVcsManager.getInstance(project).getAnnotationLocalChangesListener().registerAnnotation(fileAnnotation.getFile(), fileAnnotation);
243 final EditorGutterComponentEx editorGutter = (EditorGutterComponentEx)editor.getGutter();
244 final List<AnnotationFieldGutter> gutters = new ArrayList<AnnotationFieldGutter>();
245 final AnnotationSourceSwitcher switcher = fileAnnotation.getAnnotationSourceSwitcher();
246 final UpToDateLineNumberProvider getUpToDateLineNumber = new UpToDateLineNumberProviderImpl(editor.getDocument(), project);
248 final AnnotationPresentation presentation = new AnnotationPresentation(fileAnnotation, getUpToDateLineNumber, switcher);
249 if (vcs.getCommittedChangesProvider() != null) {
250 presentation.addAction(new ShowDiffFromAnnotation(fileAnnotation, vcs, currentFile));
252 presentation.addAction(new CopyRevisionNumberFromAnnotateAction(fileAnnotation));
253 presentation.addAction(Separator.getInstance());
255 final Couple<Map<VcsRevisionNumber, Color>> bgColorMap =
256 Registry.is("vcs.show.colored.annotations") ? computeBgColors(fileAnnotation) : null;
257 final Map<VcsRevisionNumber, Integer> historyIds = Registry.is("vcs.show.history.numbers") ? computeLineNumbers(fileAnnotation) : null;
259 if (switcher != null) {
260 switcher.switchTo(switcher.getDefaultSource());
261 final LineAnnotationAspect revisionAspect = switcher.getRevisionAspect();
262 final CurrentRevisionAnnotationFieldGutter currentRevisionGutter =
263 new CurrentRevisionAnnotationFieldGutter(fileAnnotation, revisionAspect, presentation, bgColorMap);
264 final MergeSourceAvailableMarkerGutter mergeSourceGutter =
265 new MergeSourceAvailableMarkerGutter(fileAnnotation, null, presentation, bgColorMap);
267 presentation.addAction(new SwitchAnnotationSourceAction(switcher, editorGutter));
268 presentation.addSourceSwitchListener(currentRevisionGutter);
269 presentation.addSourceSwitchListener(mergeSourceGutter);
271 currentRevisionGutter.consume(switcher.getDefaultSource());
272 mergeSourceGutter.consume(switcher.getDefaultSource());
274 gutters.add(currentRevisionGutter);
275 gutters.add(mergeSourceGutter);
278 final LineAnnotationAspect[] aspects = fileAnnotation.getAspects();
279 for (LineAnnotationAspect aspect : aspects) {
280 gutters.add(new AnnotationFieldGutter(fileAnnotation, aspect, presentation, bgColorMap));
284 if (historyIds != null) {
285 gutters.add(new HistoryIdColumn(fileAnnotation, presentation, bgColorMap, historyIds));
287 gutters.add(new HighlightedAdditionalColumn(fileAnnotation, null, presentation, bgColorMap));
288 final AnnotateActionGroup actionGroup = new AnnotateActionGroup(gutters, editorGutter);
289 presentation.addAction(actionGroup, 1);
290 gutters.add(new ExtraFieldGutter(fileAnnotation, presentation, bgColorMap, actionGroup));
292 presentation.addAction(new AnnotateCurrentRevisionAction(fileAnnotation, vcs));
293 presentation.addAction(new AnnotatePreviousRevisionAction(fileAnnotation, vcs));
294 addActionsFromExtensions(presentation, fileAnnotation);
296 for (AnnotationFieldGutter gutter : gutters) {
297 final AnnotationGutterLineConvertorProxy proxy = new AnnotationGutterLineConvertorProxy(getUpToDateLineNumber, gutter);
298 if (gutter.isGutterAction()) {
299 editor.getGutter().registerTextAnnotation(proxy, proxy);
302 editor.getGutter().registerTextAnnotation(proxy);
307 private static void addActionsFromExtensions(@NotNull AnnotationPresentation presentation, @NotNull FileAnnotation fileAnnotation) {
308 AnnotationGutterActionProvider[] extensions = AnnotationGutterActionProvider.EP_NAME.getExtensions();
309 if (extensions.length > 0) {
310 presentation.addAction(new Separator());
312 for (AnnotationGutterActionProvider provider : extensions) {
313 presentation.addAction(provider.createAction(fileAnnotation));
318 private static Map<VcsRevisionNumber, Integer> computeLineNumbers(@NotNull FileAnnotation fileAnnotation) {
319 final Map<VcsRevisionNumber, Integer> numbers = new HashMap<VcsRevisionNumber, Integer>();
320 final List<VcsFileRevision> fileRevisionList = fileAnnotation.getRevisions();
321 if (fileRevisionList != null) {
322 int size = fileRevisionList.size();
323 for (int i = 0; i < size; i++) {
324 VcsFileRevision revision = fileRevisionList.get(i);
325 final VcsRevisionNumber number = revision.getRevisionNumber();
327 numbers.put(number, size - i);
330 return numbers.size() < 2 ? null : numbers;
334 private static Couple<Map<VcsRevisionNumber, Color>> computeBgColors(@NotNull FileAnnotation fileAnnotation) {
335 final Map<VcsRevisionNumber, Color> commitOrderColors = new HashMap<VcsRevisionNumber, Color>();
336 final Map<VcsRevisionNumber, Color> commitAuthorColors = new HashMap<VcsRevisionNumber, Color>();
337 final Map<String, Color> authorColors = new HashMap<String, Color>();
338 final List<VcsFileRevision> fileRevisionList = fileAnnotation.getRevisions();
339 if (fileRevisionList != null) {
340 final int colorsCount = BG_COLORS.length;
341 final int revisionsCount = fileRevisionList.size();
343 for (int i = 0; i < fileRevisionList.size(); i++) {
344 VcsFileRevision revision = fileRevisionList.get(i);
345 final VcsRevisionNumber number = revision.getRevisionNumber();
346 final String author = revision.getAuthor();
347 if (number == null) continue;
349 if (!commitAuthorColors.containsKey(number)) {
350 if (author != null && !authorColors.containsKey(author)) {
351 final int index = authorColors.size();
352 Color color = BG_COLORS[index * BG_COLORS_PRIME % colorsCount];
353 authorColors.put(author, color);
356 commitAuthorColors.put(number, authorColors.get(author));
358 if (!commitOrderColors.containsKey(number)) {
359 Color color = BG_COLORS[colorsCount * i / revisionsCount];
360 commitOrderColors.put(number, color);
364 return Couple.of(commitOrderColors.size() > 1 ? commitOrderColors : null,
365 commitAuthorColors.size() > 1 ? commitAuthorColors : null);