diff --git a/platform/vcs-log/impl/src/META-INF/vcs-log.xml b/platform/vcs-log/impl/src/META-INF/vcs-log.xml index 399b350e3626..3a5fc95eaef1 100644 --- a/platform/vcs-log/impl/src/META-INF/vcs-log.xml +++ b/platform/vcs-log/impl/src/META-INF/vcs-log.xml @@ -156,6 +156,7 @@ + @@ -195,6 +196,7 @@ + diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/history/FileHistoryDiffPreview.kt b/platform/vcs-log/impl/src/com/intellij/vcs/log/history/FileHistoryDiffPreview.kt new file mode 100644 index 000000000000..22f819f216f1 --- /dev/null +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/history/FileHistoryDiffPreview.kt @@ -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 = allChanges + + override fun getAllChanges(): List { + 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) + } + } +} diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/history/FileHistoryUi.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/history/FileHistoryUi.java index c28a18efdebf..fd861c0284a4 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/history/FileHistoryUi.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/history/FileHistoryUi.java @@ -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 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)); + } } } diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/history/FileHistoryUiProperties.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/history/FileHistoryUiProperties.java index 0b228743bade..4ceb3fe4cedd 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/history/FileHistoryUiProperties.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/history/FileHistoryUiProperties.java @@ -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 COLUMN_WIDTH = ContainerUtil.newHashMap(); public List COLUMN_ORDER = ContainerUtil.newArrayList(); + public boolean SHOW_DIFF_PREVIEW = true; } @SuppressWarnings("unchecked") @NotNull @Override public T get(@NotNull VcsLogUiProperty 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 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 void set(@NotNull VcsLogUiProperty 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)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 boolean exists(@NotNull VcsLogUiProperty 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; } diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/actions/ShowDiffPreviewAction.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/actions/ShowDiffPreviewAction.java index d2d062a27407..207478e7a6f1 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/actions/ShowDiffPreviewAction.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/actions/ShowDiffPreviewAction.java @@ -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 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); + } + } } diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/frame/VcsLogChangesBrowser.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/frame/VcsLogChangesBrowser.java index c25ec787910b..3eadd8d4650f 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/frame/VcsLogChangesBrowser.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/frame/VcsLogChangesBrowser.java @@ -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 getter, - @NotNull Disposable parent) { + @NotNull MainVcsLogUiProperties uiProperties, + @NotNull Function 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 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 context) { + @Nullable + public static ChangeDiffRequestChain.Producer createDiffRequestProducer(@NotNull Project project, + @NotNull Change change, + @NotNull Map 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 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 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 context) { + private static void putFilePathsIntoChangeContext(@NotNull Change change, @NotNull Map context) { ContentRevision afterRevision = change.getAfterRevision(); ContentRevision beforeRevision = change.getBeforeRevision(); FilePath aFile = afterRevision == null ? null : afterRevision.getFile();