[file-history] introduce diff preview

IDEA-170988
This commit is contained in:
Julia Beliaeva
2018-09-25 22:00:29 +03:00
parent 93c1fb2283
commit 3862ec2a0d
6 changed files with 183 additions and 50 deletions
@@ -156,6 +156,7 @@
<reference id="Vcs.Log.ShowOtherBranches"/>
<separator/>
<reference id="Vcs.Log.ShowDetailsAction"/>
<reference id="Vcs.Log.ShowDiffPreview"/>
<separator/>
<reference id="VcsHistoryActionsGroup.Toolbar"/>
<separator/>
@@ -195,6 +196,7 @@
<reference id="Vcs.Log.ShowOtherBranches"/>
<reference id="Vcs.Log.Refresh"/>
<reference id="Vcs.Log.ShowDetailsAction"/>
<reference id="Vcs.Log.ShowDiffPreview"/>
<reference id="Vcs.FileHistory.ContextMenu"/>
<reference id="Vcs.FileHistory.Toolbar"/>
<reference id="VcsHistoryActionsGroup"/>
@@ -0,0 +1,51 @@
// 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 com.intellij.vcs.log.history
import com.intellij.diff.chains.DiffRequestProducer
import com.intellij.diff.util.DiffPlaces
import com.intellij.openapi.Disposable
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.vcs.changes.Change
import com.intellij.openapi.vcs.changes.ChangeViewDiffRequestProcessor
import com.intellij.ui.IdeBorderFactory
import com.intellij.ui.SideBorder
import com.intellij.util.containers.ContainerUtil
import com.intellij.vcs.log.ui.frame.VcsLogChangesBrowser
internal class FileHistoryDiffPreview(project: Project, private val changeGetter: () -> Change?,
disposable: Disposable) : ChangeViewDiffRequestProcessor(project, DiffPlaces.VCS_LOG_VIEW) {
init {
myContentPanel.border = IdeBorderFactory.createBorder(SideBorder.TOP)
Disposer.register(disposable, this)
}
override fun getSelectedChanges(): List<ChangeViewDiffRequestProcessor.Wrapper> = allChanges
override fun getAllChanges(): List<ChangeViewDiffRequestProcessor.Wrapper> {
val change = changeGetter() ?: return emptyList()
return listOf(MyChangeWrapper(change))
}
override fun selectChange(change: ChangeViewDiffRequestProcessor.Wrapper) {}
fun updatePreview(state: Boolean) {
if (state) {
refresh(false)
}
else {
clear()
}
}
private inner class MyChangeWrapper internal constructor(private val change: Change) : ChangeViewDiffRequestProcessor.Wrapper() {
override fun getUserObject(): Any {
return change
}
override fun createProducer(project: Project?): DiffRequestProducer? {
return VcsLogChangesBrowser.createDiffRequestProducer(project!!, change, ContainerUtil.newHashMap(), true)
}
}
}
@@ -2,6 +2,7 @@
package com.intellij.vcs.log.history;
import com.google.common.util.concurrent.SettableFuture;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Conditions;
import com.intellij.openapi.vcs.FilePath;
@@ -9,6 +10,7 @@ import com.intellij.openapi.vcs.changes.Change;
import com.intellij.openapi.vcs.history.VcsFileRevision;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.JBColor;
import com.intellij.ui.OnePixelSplitter;
import com.intellij.util.PairFunction;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.vcs.log.*;
@@ -33,11 +35,13 @@ import com.intellij.vcs.log.visible.VisiblePackRefresher;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.event.ListSelectionListener;
import java.awt.*;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.*;
import static com.intellij.util.ObjectUtils.notNull;
import static com.intellij.util.ObjectUtils.notNull;
@@ -51,6 +55,9 @@ public class FileHistoryUi extends AbstractVcsLogUi {
@NotNull private final FileHistoryUiProperties myUiProperties;
@NotNull private final FileHistoryFilterUi myFilterUi;
@NotNull private final FileHistoryPanel myFileHistoryPanel;
@Nullable private final FileHistoryDiffPreview myDiffPreview;
@Nullable private final OnePixelSplitter myDiffPreviewSplitter;
@NotNull private final JComponent myMainComponent;
@NotNull private final Set<String> myHighlighterIds;
@NotNull private final MyPropertiesChangeListener myPropertiesChangeListener;
@@ -73,6 +80,27 @@ public class FileHistoryUi extends AbstractVcsLogUi {
myFilterUi = new FileHistoryFilterUi(path, revision, root, uiProperties);
myFileHistoryPanel = new FileHistoryPanel(this, logData, myVisiblePack, path);
if (!myPath.isDirectory()) {
myDiffPreview = new FileHistoryDiffPreview(myProject, () -> getSelectedChange(), this);
ListSelectionListener selectionListener = e -> {
int[] selection = getTable().getSelectedRows();
ApplicationManager.getApplication()
.invokeLater(() -> myDiffPreview.updatePreview(myUiProperties.get(CommonUiProperties.SHOW_DIFF_PREVIEW)),
o -> !Arrays.equals(selection, getTable().getSelectedRows()));
};
getTable().getSelectionModel().addListSelectionListener(selectionListener);
myDiffPreviewSplitter = new OnePixelSplitter(false, "vcs.history.diff.splitter.proportion", 0.7f);
myDiffPreviewSplitter.setFirstComponent(myFileHistoryPanel);
showDiffPreview(myUiProperties.get(CommonUiProperties.SHOW_DIFF_PREVIEW));
myMainComponent = myDiffPreviewSplitter;
}
else {
myDiffPreview = null;
myDiffPreviewSplitter = null;
myMainComponent = myFileHistoryPanel;
}
myHighlighterIds = myRevision == null
? ContainerUtil.newHashSet(MyCommitsHighlighter.Factory.ID,
CurrentBranchHighlighter.Factory.ID)
@@ -94,6 +122,10 @@ public class FileHistoryUi extends AbstractVcsLogUi {
return path.getPath() + (revision == null ? "" : revision.asString());
}
public boolean hasDiffPreview() {
return myDiffPreview != null;
}
@Nullable
public VcsFileRevision createRevision(@Nullable VcsCommitMetadata commit) {
if (commit == null) return null;
@@ -195,6 +227,13 @@ public class FileHistoryUi extends AbstractVcsLogUi {
return myPath.equals(targetPath) && Objects.equals(myRevision, targetRevision);
}
private void showDiffPreview(boolean state) {
if (myDiffPreview != null) {
myDiffPreview.updatePreview(state);
myDiffPreviewSplitter.setSecondComponent(state ? myDiffPreview.getComponent() : null);
}
}
@NotNull
@Override
public VcsLogFilterUi getFilterUi() {
@@ -209,6 +248,9 @@ public class FileHistoryUi extends AbstractVcsLogUi {
@Override
protected void onVisiblePackUpdated(boolean permGraphChanged) {
myFileHistoryPanel.updateDataPack(myVisiblePack, permGraphChanged);
if (myDiffPreview != null) {
myDiffPreview.updatePreview(myUiProperties.get(CommonUiProperties.SHOW_DIFF_PREVIEW));
}
}
@NotNull
@@ -220,7 +262,7 @@ public class FileHistoryUi extends AbstractVcsLogUi {
@NotNull
@Override
public Component getMainComponent() {
return myFileHistoryPanel;
return myMainComponent;
}
@Nullable
@@ -260,6 +302,9 @@ public class FileHistoryUi extends AbstractVcsLogUi {
else if (property instanceof CommonUiProperties.TableColumnProperty) {
getTable().forceReLayout(((CommonUiProperties.TableColumnProperty)property).getColumn());
}
else if (CommonUiProperties.SHOW_DIFF_PREVIEW.equals(property)) {
showDiffPreview(myUiProperties.get(CommonUiProperties.SHOW_DIFF_PREVIEW));
}
}
}
@@ -8,8 +8,7 @@ import com.intellij.openapi.components.Storage;
import com.intellij.openapi.components.StoragePathMacros;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.ContainerUtilRt;
import com.intellij.vcs.log.impl.CommonUiProperties;
import com.intellij.vcs.log.impl.CommonUiProperties.TableColumnProperty;
import com.intellij.vcs.log.impl.CommonUiProperties.*;
import com.intellij.vcs.log.impl.VcsLogUiProperties;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -18,6 +17,7 @@ import java.util.Collection;
import java.util.List;
import java.util.Map;
import static com.intellij.vcs.log.impl.CommonUiProperties.*;
import static com.intellij.vcs.log.ui.table.GraphTableModel.*;
@State(name = "Vcs.Log.History.Properties", storages = {@Storage(file = StoragePathMacros.WORKSPACE_FILE)})
@@ -31,19 +31,20 @@ public class FileHistoryUiProperties implements VcsLogUiProperties, PersistentSt
public boolean SHOW_OTHER_BRANCHES = false;
public Map<Integer, Integer> COLUMN_WIDTH = ContainerUtil.newHashMap();
public List<Integer> COLUMN_ORDER = ContainerUtil.newArrayList();
public boolean SHOW_DIFF_PREVIEW = true;
}
@SuppressWarnings("unchecked")
@NotNull
@Override
public <T> T get(@NotNull VcsLogUiProperty<T> property) {
if (CommonUiProperties.SHOW_DETAILS.equals(property)) {
if (SHOW_DETAILS.equals(property)) {
return (T)Boolean.valueOf(myState.SHOW_DETAILS);
}
else if (SHOW_ALL_BRANCHES.equals(property)) {
return (T)Boolean.valueOf(myState.SHOW_OTHER_BRANCHES);
}
else if (CommonUiProperties.COLUMN_ORDER.equals(property)) {
else if (COLUMN_ORDER.equals(property)) {
List<Integer> order = myState.COLUMN_ORDER;
if (order == null || order.isEmpty()) {
order = ContainerUtilRt.newArrayList(ROOT_COLUMN, AUTHOR_COLUMN, DATE_COLUMN, COMMIT_COLUMN);
@@ -55,24 +56,30 @@ public class FileHistoryUiProperties implements VcsLogUiProperties, PersistentSt
if (savedWidth == null) return (T)Integer.valueOf(-1);
return (T)savedWidth;
}
else if (SHOW_DIFF_PREVIEW.equals(property)) {
return (T)Boolean.valueOf(myState.SHOW_DIFF_PREVIEW);
}
throw new UnsupportedOperationException("Unknown property " + property);
}
@SuppressWarnings("unchecked")
@Override
public <T> void set(@NotNull VcsLogUiProperty<T> property, @NotNull T value) {
if (CommonUiProperties.SHOW_DETAILS.equals(property)) {
if (SHOW_DETAILS.equals(property)) {
myState.SHOW_DETAILS = (Boolean)value;
}
else if (SHOW_ALL_BRANCHES.equals(property)) {
myState.SHOW_OTHER_BRANCHES = (Boolean)value;
}
else if (CommonUiProperties.COLUMN_ORDER.equals(property)) {
else if (COLUMN_ORDER.equals(property)) {
myState.COLUMN_ORDER = (List<Integer>)value;
}
else if (property instanceof TableColumnProperty) {
myState.COLUMN_WIDTH.put(((TableColumnProperty)property).getColumn(), (Integer)value);
}
else if (SHOW_DIFF_PREVIEW.equals(property)) {
myState.SHOW_DIFF_PREVIEW = (Boolean)value;
}
else {
throw new UnsupportedOperationException("Unknown property " + property);
}
@@ -81,9 +88,10 @@ public class FileHistoryUiProperties implements VcsLogUiProperties, PersistentSt
@Override
public <T> boolean exists(@NotNull VcsLogUiProperty<T> property) {
return CommonUiProperties.SHOW_DETAILS.equals(property) ||
return SHOW_DETAILS.equals(property) ||
SHOW_ALL_BRANCHES.equals(property) ||
CommonUiProperties.COLUMN_ORDER.equals(property) ||
COLUMN_ORDER.equals(property) ||
SHOW_DIFF_PREVIEW.equals(property) ||
property instanceof TableColumnProperty;
}
@@ -2,17 +2,32 @@
package com.intellij.vcs.log.ui.actions;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.vcs.log.history.FileHistoryUi;
import com.intellij.vcs.log.impl.CommonUiProperties;
import com.intellij.vcs.log.impl.VcsLogUiProperties;
import com.intellij.vcs.log.ui.VcsLogInternalDataKeys;
import org.jetbrains.annotations.NotNull;
public class ShowDiffPreviewAction extends BooleanPropertyToggleAction {
public ShowDiffPreviewAction() {
super("Preview Diff", "Show Diff Preview Panel in Vcs Log", AllIcons.Actions.PreviewDetails);
super("Preview Diff", "Show Diff Preview Panel", AllIcons.Actions.PreviewDetails);
}
@Override
protected VcsLogUiProperties.VcsLogUiProperty<Boolean> getProperty() {
return CommonUiProperties.SHOW_DIFF_PREVIEW;
}
@Override
public void update(@NotNull AnActionEvent e) {
FileHistoryUi fileHistoryUi = e.getData(VcsLogInternalDataKeys.FILE_HISTORY_UI);
if (fileHistoryUi != null && !fileHistoryUi.hasDiffPreview()) {
e.getPresentation().setEnabledAndVisible(false);
}
else {
super.update(e);
}
}
}
@@ -54,7 +54,7 @@ import static com.intellij.vcs.log.impl.MainVcsLogUiProperties.SHOW_CHANGES_FROM
/**
* Change browser for commits in the Log. For merge commits, can display changes to commits parents in separate groups.
*/
class VcsLogChangesBrowser extends ChangesBrowserBase implements Disposable {
public class VcsLogChangesBrowser extends ChangesBrowserBase implements Disposable {
@NotNull private static final String EMPTY_SELECTION_TEXT = "Select commit to view details";
@NotNull private final Project myProject;
@NotNull private final MainVcsLogUiProperties myUiProperties;
@@ -69,9 +69,9 @@ class VcsLogChangesBrowser extends ChangesBrowserBase implements Disposable {
@Nullable private Runnable myModelUpdateListener;
VcsLogChangesBrowser(@NotNull Project project,
@NotNull MainVcsLogUiProperties uiProperties,
@NotNull Function<CommitId, VcsShortCommitDetails> getter,
@NotNull Disposable parent) {
@NotNull MainVcsLogUiProperties uiProperties,
@NotNull Function<CommitId, VcsShortCommitDetails> getter,
@NotNull Disposable parent) {
super(project, false, false);
myProject = project;
myUiProperties = uiProperties;
@@ -258,42 +258,54 @@ class VcsLogChangesBrowser extends ChangesBrowserBase implements Disposable {
@Nullable
public ChangeDiffRequestChain.Producer getDiffRequestProducer(@NotNull Object userObject, boolean forDiffPreview) {
if (!(userObject instanceof Change)) return null;
Change change = (Change)userObject;
Map<Key, Object> context = ContainerUtil.newHashMap();
if (userObject instanceof MergedChange) {
MergedChange mergedChange = (MergedChange)userObject;
if (mergedChange.getSourceChanges().size() == 2) {
if (forDiffPreview) {
putFilePathsIntoContext(mergedChange, context);
}
return new MergedChangeDiffRequestProvider.MyProducer(myProject, mergedChange, context);
}
if (!(change instanceof MergedChange)) {
putRootTagIntoChangeContext(change, context);
}
if (userObject instanceof Change) {
Change change = (Change)userObject;
if (forDiffPreview) {
putFilePathsIntoContext(change, context);
}
CommitId parentId = null;
for (CommitId commitId : myChangesToParents.keySet()) {
if (myChangesToParents.get(commitId).contains(change)) {
parentId = commitId;
break;
}
}
if (parentId != null) {
RootTag tag = new RootTag(parentId.getHash(), getText(parentId));
context.put(ChangeDiffRequestProducer.TAG_KEY, tag);
}
return ChangeDiffRequestProducer.create(myProject, change, context);
}
return null;
return createDiffRequestProducer(myProject, change, context, forDiffPreview);
}
private static void putFilePathsIntoContext(@NotNull MergedChange change, @NotNull Map<Key, Object> context) {
@Nullable
public static ChangeDiffRequestChain.Producer createDiffRequestProducer(@NotNull Project project,
@NotNull Change change,
@NotNull Map<Key, Object> context,
boolean forDiffPreview) {
if (change instanceof MergedChange) {
MergedChange mergedChange = (MergedChange)change;
if (mergedChange.getSourceChanges().size() == 2) {
if (forDiffPreview) {
putFilePathsIntoMergedChangeContext(mergedChange, context);
}
return new MergedChangeDiffRequestProvider.MyProducer(project, mergedChange, context);
}
}
if (forDiffPreview) {
putFilePathsIntoChangeContext(change, context);
}
return ChangeDiffRequestProducer.create(project, change, context);
}
private void putRootTagIntoChangeContext(@NotNull Change change, @NotNull Map<Key, Object> context) {
CommitId parentId = null;
for (CommitId commitId : myChangesToParents.keySet()) {
if (myChangesToParents.get(commitId).contains(change)) {
parentId = commitId;
break;
}
}
if (parentId != null) {
RootTag tag = new RootTag(parentId.getHash(), getText(parentId));
context.put(ChangeDiffRequestProducer.TAG_KEY, tag);
}
}
private static void putFilePathsIntoMergedChangeContext(@NotNull MergedChange change, @NotNull Map<Key, Object> context) {
ContentRevision centerRevision = change.getAfterRevision();
ContentRevision leftRevision = change.getSourceChanges().get(0).getBeforeRevision();
ContentRevision rightRevision = change.getSourceChanges().get(1).getBeforeRevision();
@@ -305,7 +317,7 @@ class VcsLogChangesBrowser extends ChangesBrowserBase implements Disposable {
context.put(VCS_DIFF_LEFT_CONTENT_TITLE, getRevisionTitle(leftRevision, leftFile, centerFile == null ? rightFile : centerFile));
}
private static void putFilePathsIntoContext(@NotNull Change change, @NotNull Map<Key, Object> context) {
private static void putFilePathsIntoChangeContext(@NotNull Change change, @NotNull Map<Key, Object> context) {
ContentRevision afterRevision = change.getAfterRevision();
ContentRevision beforeRevision = change.getBeforeRevision();
FilePath aFile = afterRevision == null ? null : afterRevision.getFile();