diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/annotate/FileAnnotation.java b/platform/vcs-api/src/com/intellij/openapi/vcs/annotate/FileAnnotation.java index c97b4a6a7394..45ddcc89c84d 100644 --- a/platform/vcs-api/src/com/intellij/openapi/vcs/annotate/FileAnnotation.java +++ b/platform/vcs-api/src/com/intellij/openapi/vcs/annotate/FileAnnotation.java @@ -25,7 +25,7 @@ import com.intellij.openapi.vcs.history.VcsRevisionNumber; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.Consumer; import com.intellij.util.containers.ContainerUtil; -import java.util.HashMap; +import org.jetbrains.annotations.CalledInAwt; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -195,9 +195,10 @@ public abstract class FileAnnotation { * If `this` is visible, hide it and show new one instead. * If `this` is not visible, do nothing. * - * @param newFileAnnotation annotations to be shown + * @param newFileAnnotation annotations to be shown or `null` to load annotations again */ - public synchronized final void reload(@NotNull FileAnnotation newFileAnnotation) { + @CalledInAwt + public synchronized final void reload(@Nullable FileAnnotation newFileAnnotation) { if (myReloader != null) myReloader.consume(newFileAnnotation); } diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/history/VcsHistoryCache.java b/platform/vcs-api/src/com/intellij/openapi/vcs/history/VcsHistoryCache.java index 0466a84ef3e1..5169e5753a85 100644 --- a/platform/vcs-api/src/com/intellij/openapi/vcs/history/VcsHistoryCache.java +++ b/platform/vcs-api/src/com/intellij/openapi/vcs/history/VcsHistoryCache.java @@ -121,6 +121,12 @@ public class VcsHistoryCache { } } + public void clearAnnotations() { + synchronized (myLock) { + myAnnotationCache.clear(); + } + } + public static class CachedHistory { private final FilePath myPath; private final List myRevisions; diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/actions/AnnotateDiffViewerAction.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/actions/AnnotateDiffViewerAction.java index 3ad5f9c9b474..cb3c89847da3 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/actions/AnnotateDiffViewerAction.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/actions/AnnotateDiffViewerAction.java @@ -142,8 +142,7 @@ public class AnnotateDiffViewerAction { EventData data = collectEventData(e); assert data != null; - boolean annotationShown = data.annotator.isAnnotationShown(); - if (annotationShown) { + if (!selected) { data.annotator.hideAnnotation(); } else { diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/actions/AnnotateToggleAction.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/actions/AnnotateToggleAction.java index 65f0f6e2d742..42484ea61c4e 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/actions/AnnotateToggleAction.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/actions/AnnotateToggleAction.java @@ -15,11 +15,9 @@ */ package com.intellij.openapi.vcs.actions; +import com.intellij.ide.DataManager; import com.intellij.openapi.Disposable; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.CommonDataKeys; -import com.intellij.openapi.actionSystem.Separator; -import com.intellij.openapi.actionSystem.ToggleAction; +import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.colors.EditorColorsScheme; import com.intellij.openapi.extensions.ExtensionPointName; @@ -142,9 +140,24 @@ public class AnnotateToggleAction extends ToggleAction implements DumbAware { }); fileAnnotation.setReloader(newFileAnnotation -> { + if (project.isDisposed()) return; if (editor.getGutter().isAnnotationsShown()) { - assert Comparing.equal(fileAnnotation.getFile(), newFileAnnotation.getFile()); - doAnnotate(editor, project, currentFile, newFileAnnotation, vcs, upToDateLineNumbers, false); + if (newFileAnnotation != null) { + assert Comparing.equal(fileAnnotation.getFile(), newFileAnnotation.getFile()); + doAnnotate(editor, project, currentFile, newFileAnnotation, vcs, upToDateLineNumbers, false); + } + else { + DataContext dataContext = DataManager.getInstance().getDataContext(editor.getComponent()); + AnActionEvent event = AnActionEvent.createFromDataContext(ActionPlaces.UNKNOWN, null, dataContext); + Provider provider = getProvider(event); + + if (provider != null && provider.isEnabled(event) && !provider.isSuspended(event)) { + provider.perform(event, true); + } + else { + editor.getGutter().closeAllAnnotations(); + } + } } }); diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/VcsAnnotationLocalChangesListenerImpl.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/VcsAnnotationLocalChangesListenerImpl.java index ddcdeffca382..3e38d73b5b3b 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/VcsAnnotationLocalChangesListenerImpl.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/VcsAnnotationLocalChangesListenerImpl.java @@ -17,6 +17,7 @@ package com.intellij.openapi.vcs.changes; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.ZipperUpdater; import com.intellij.openapi.vcs.ProjectLevelVcsManager; @@ -39,7 +40,11 @@ import org.jetbrains.annotations.TestOnly; import java.io.File; import java.util.*; +import static com.intellij.openapi.diagnostic.Logger.getInstance; + public class VcsAnnotationLocalChangesListenerImpl implements Disposable, VcsAnnotationLocalChangesListener { + private static final Logger LOG = getInstance(VcsAnnotationLocalChangesListenerImpl.class); + private final ZipperUpdater myUpdater; private final MessageBusConnection myConnection; @@ -132,11 +137,8 @@ public class VcsAnnotationLocalChangesListenerImpl implements Disposable, VcsAnn final VcsRevisionNumber number = fromDiffProvider(key); if (number == null) continue; final Collection fileAnnotations = entry.getValue(); - for (FileAnnotation annotation : fileAnnotations) { - if (annotation.isBaseRevisionChanged(number)) { - annotation.close(); - } - } + List copy = ContainerUtil.filter(fileAnnotations, it -> it.isBaseRevisionChanged(number)); + invalidateAnnotations(copy); } } } @@ -162,11 +164,9 @@ public class VcsAnnotationLocalChangesListenerImpl implements Disposable, VcsAnn } if (number == null) return; - for (FileAnnotation annotation : annotations) { - if (annotation.isBaseRevisionChanged(number)) { - annotation.close(); - } - } + VcsRevisionNumber finalNumber = number; + List copy = ContainerUtil.filter(annotations, it -> it.isBaseRevisionChanged(finalNumber)); + invalidateAnnotations(copy); } } @@ -181,18 +181,25 @@ public class VcsAnnotationLocalChangesListenerImpl implements Disposable, VcsAnn private void closeForVcs(final Set refresh) { if (refresh.isEmpty()) return; - final Set copy = new HashSet<>(); synchronized (myLock) { - for (FileAnnotation annotation : myFileAnnotationMap.values()) { - final VcsKey key = annotation.getVcsKey(); - if (key != null && refresh.contains(key)) { - copy.add(annotation); + List copy = ContainerUtil.filter(myFileAnnotationMap.values(), it -> { + return it.getVcsKey() != null && refresh.contains(it.getVcsKey()); + }); + invalidateAnnotations(copy); + } + } + + private static void invalidateAnnotations(@NotNull Collection annotations) { + ApplicationManager.getApplication().invokeLater(() -> { + for (FileAnnotation annotation: annotations) { + try { + annotation.reload(null); + } + catch (Exception e) { + LOG.error(e); } } - } - for (FileAnnotation annotation : copy) { - annotation.close(); - } + }); } // annotations for already committed revisions should not register with this method - they are not subject to refresh diff --git a/plugins/git4idea/src/META-INF/plugin.xml b/plugins/git4idea/src/META-INF/plugin.xml index 576803fa19b4..bce7d49bb45d 100644 --- a/plugins/git4idea/src/META-INF/plugin.xml +++ b/plugins/git4idea/src/META-INF/plugin.xml @@ -165,6 +165,7 @@ + diff --git a/plugins/git4idea/src/git4idea/actions/GitToggleAnnotationOptionsActionProvider.java b/plugins/git4idea/src/git4idea/actions/GitToggleAnnotationOptionsActionProvider.java new file mode 100644 index 000000000000..fce80adee910 --- /dev/null +++ b/plugins/git4idea/src/git4idea/actions/GitToggleAnnotationOptionsActionProvider.java @@ -0,0 +1,128 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package git4idea.actions; + +import com.intellij.openapi.actionSystem.ActionGroup; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.ToggleAction; +import com.intellij.openapi.progress.util.BackgroundTaskUtil; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.ProjectLevelVcsManager; +import com.intellij.openapi.vcs.annotate.AnnotationGutterActionProvider; +import com.intellij.openapi.vcs.annotate.FileAnnotation; +import com.intellij.openapi.vcs.changes.VcsAnnotationRefresher; +import git4idea.GitVcs; +import git4idea.annotate.GitFileAnnotation; +import git4idea.config.GitVcsApplicationSettings; +import git4idea.config.GitVcsApplicationSettings.AnnotateDetectMovementsOption; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class GitToggleAnnotationOptionsActionProvider implements AnnotationGutterActionProvider { + private static final GitVcsApplicationSettings SETTINGS = GitVcsApplicationSettings.getInstance(); + + @NotNull + @Override + public AnAction createAction(@NotNull final FileAnnotation annotation) { + return new MyGroup(annotation); + } + + private static void resetAllAnnotations(@NotNull Project project) { + ProjectLevelVcsManager.getInstance(project).getVcsHistoryCache().clearAnnotations(); + BackgroundTaskUtil.syncPublisher(project, VcsAnnotationRefresher.LOCAL_CHANGES_CHANGED).configurationChanged(GitVcs.getKey()); + } + + private static class MyGroup extends ActionGroup { + private final FileAnnotation myAnnotation; + + public MyGroup(@NotNull FileAnnotation annotation) { + super("Options", true); + myAnnotation = annotation; + } + + @NotNull + @Override + public AnAction[] getChildren(@Nullable AnActionEvent e) { + if (myAnnotation instanceof GitFileAnnotation) { + return new AnAction[]{ + new ToggleIgnoreWhitespaces(myAnnotation.getProject()), + new ToggleInnerMovementsWhitespaces(myAnnotation.getProject()), + new ToggleOuterMovementsWhitespaces(myAnnotation.getProject()), + }; + } + return AnAction.EMPTY_ARRAY; + } + } + + private static class ToggleIgnoreWhitespaces extends ToggleAction implements DumbAware { + @NotNull private final Project myProject; + + public ToggleIgnoreWhitespaces(@NotNull Project project) { + super("Ignore Whitespaces"); + myProject = project; + } + + @Override + public boolean isSelected(AnActionEvent e) { + return SETTINGS.isIgnoreWhitespaces(); + } + + @Override + public void setSelected(AnActionEvent e, boolean enabled) { + SETTINGS.setIgnoreWhitespaces(enabled); + resetAllAnnotations(myProject); + } + } + + private static class ToggleInnerMovementsWhitespaces extends ToggleAction implements DumbAware { + @NotNull private final Project myProject; + + public ToggleInnerMovementsWhitespaces(@NotNull Project project) { + super("Detect Movements Within File"); + myProject = project; + } + + @Override + public boolean isSelected(AnActionEvent e) { + return SETTINGS.getAnnotateDetectMovementsOption() == AnnotateDetectMovementsOption.INNER || + SETTINGS.getAnnotateDetectMovementsOption() == AnnotateDetectMovementsOption.OUTER; + } + + @Override + public void setSelected(AnActionEvent e, boolean enabled) { + if (enabled) { + SETTINGS.setAnnotateDetectMovementsOption(AnnotateDetectMovementsOption.INNER); + } + else { + SETTINGS.setAnnotateDetectMovementsOption(AnnotateDetectMovementsOption.NONE); + } + resetAllAnnotations(myProject); + } + } + + private static class ToggleOuterMovementsWhitespaces extends ToggleAction implements DumbAware { + @NotNull private final Project myProject; + + public ToggleOuterMovementsWhitespaces(@NotNull Project project) { + super("Detect Movements Across Files"); + myProject = project; + } + + @Override + public boolean isSelected(AnActionEvent e) { + return SETTINGS.getAnnotateDetectMovementsOption() == AnnotateDetectMovementsOption.OUTER; + } + + @Override + public void setSelected(AnActionEvent e, boolean enabled) { + if (enabled) { + SETTINGS.setAnnotateDetectMovementsOption(AnnotateDetectMovementsOption.OUTER); + } + else { + SETTINGS.setAnnotateDetectMovementsOption(AnnotateDetectMovementsOption.INNER); + } + resetAllAnnotations(myProject); + } + } +} diff --git a/plugins/git4idea/src/git4idea/annotate/GitAnnotationProvider.java b/plugins/git4idea/src/git4idea/annotate/GitAnnotationProvider.java index fb3c76f1c9f8..8ff787b1bd74 100644 --- a/plugins/git4idea/src/git4idea/annotate/GitAnnotationProvider.java +++ b/plugins/git4idea/src/git4idea/annotate/GitAnnotationProvider.java @@ -47,6 +47,8 @@ import git4idea.annotate.GitFileAnnotation.LineInfo; import git4idea.commands.Git; import git4idea.commands.GitCommand; import git4idea.commands.GitLineHandler; +import git4idea.config.GitVcsApplicationSettings; +import git4idea.config.GitVcsApplicationSettings.AnnotateDetectMovementsOption; import git4idea.history.GitFileHistory; import git4idea.history.GitHistoryProvider; import git4idea.history.GitHistoryUtils; @@ -155,8 +157,20 @@ public class GitAnnotationProvider implements AnnotationProviderEx { VirtualFile root = GitUtil.getGitRoot(repositoryFilePath); GitLineHandler h = new GitLineHandler(myProject, root, GitCommand.BLAME); h.setStdoutSuppressed(true); - h.addParameters("--porcelain", "-l", "-t", "-w"); + h.addParameters("--porcelain", "-l", "-t"); h.addParameters("--encoding=UTF-8"); + + GitVcsApplicationSettings settings = GitVcsApplicationSettings.getInstance(); + if (settings.isIgnoreWhitespaces()) { + h.addParameters("-w"); + } + if (settings.getAnnotateDetectMovementsOption() == AnnotateDetectMovementsOption.INNER) { + h.addParameters("-M"); + } + else if (settings.getAnnotateDetectMovementsOption() == AnnotateDetectMovementsOption.OUTER) { + h.addParameters("-C"); + } + if (revision == null) { h.addParameters("HEAD"); } diff --git a/plugins/git4idea/src/git4idea/annotate/GitFileAnnotation.java b/plugins/git4idea/src/git4idea/annotate/GitFileAnnotation.java index cb5d24ea94d4..d7601c86c9a0 100644 --- a/plugins/git4idea/src/git4idea/annotate/GitFileAnnotation.java +++ b/plugins/git4idea/src/git4idea/annotate/GitFileAnnotation.java @@ -16,6 +16,7 @@ package git4idea.annotate; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vcs.FilePath; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vcs.VcsKey; @@ -36,7 +37,6 @@ import git4idea.GitContentRevision; import git4idea.GitFileRevision; import git4idea.GitRevisionNumber; import git4idea.GitVcs; -import git4idea.i18n.GitBundle; import gnu.trove.TObjectIntHashMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -150,9 +150,17 @@ public class GitFileAnnotation extends FileAnnotation { fileRevision = myRevisions.get(myRevisionMap.get(revisionNumber)); } + String path = null; + if (!VcsUtil.getFilePath(myFile).equals(lineInfo.myFilePath)) { + path = FileUtil.getLocationRelativeToUserHome(lineInfo.myFilePath.getPresentableUrl()); + } + String commitMessage = fileRevision != null ? fileRevision.getCommitMessage() : lineInfo.getSubject() + "\n..."; - return GitBundle.message("annotation.tool.tip", revisionNumber.asString(), lineInfo.getAuthor(), - DateFormatUtil.formatDateTime(lineInfo.getAuthorDate()), commitMessage); + return "commit " + revisionNumber.asString() + + "\nAuthor: " + lineInfo.getAuthor() + + "\nDate: " + DateFormatUtil.formatDateTime(lineInfo.getAuthorDate()) + + (path != null ? "\nPath: " + path : "") + + "\n\n" + commitMessage; } @Nullable diff --git a/plugins/git4idea/src/git4idea/config/GitVcsApplicationSettings.java b/plugins/git4idea/src/git4idea/config/GitVcsApplicationSettings.java index 2b6784153874..d37cd2ec46df 100644 --- a/plugins/git4idea/src/git4idea/config/GitVcsApplicationSettings.java +++ b/plugins/git4idea/src/git4idea/config/GitVcsApplicationSettings.java @@ -29,6 +29,9 @@ public class GitVcsApplicationSettings implements PersistentStateComponent