From 0cbffa8f2a0a1e03194eff246df9e4eb4b032c29 Mon Sep 17 00:00:00 2001 From: Aleksey Pivovarov Date: Sun, 22 Apr 2018 20:38:09 +0300 Subject: [PATCH] IDEA-170988 vcs: add diff preview to Vcs Log commit details --- .../intellij/diff/impl/DiffSettingsHolder.kt | 7 +- .../tools/util/base/TextDiffSettingsHolder.kt | 3 + .../com/intellij/diff/util/DiffPlaces.java | 1 + .../openapi/vcs/VcsConfiguration.java | 1 + .../ChangeViewDiffRequestProcessor.java | 52 +++++++------ .../vcs/changes/ChangesViewManager.java | 6 -- .../changes/ui/CommitChangeListDialog.java | 6 -- .../intellij/vcs/log/ui/frame/MainFrame.java | 46 +++++++++++- .../log/ui/frame/VcsLogChangeProcessor.java | 74 +++++++++++++++++++ .../log/ui/frame/VcsLogChangesBrowser.java | 24 +++++- 10 files changed, 179 insertions(+), 41 deletions(-) create mode 100644 platform/vcs-log/impl/src/com/intellij/vcs/log/ui/frame/VcsLogChangeProcessor.java diff --git a/platform/diff-impl/src/com/intellij/diff/impl/DiffSettingsHolder.kt b/platform/diff-impl/src/com/intellij/diff/impl/DiffSettingsHolder.kt index a7eea8aa8b2e..376675adffc9 100644 --- a/platform/diff-impl/src/com/intellij/diff/impl/DiffSettingsHolder.kt +++ b/platform/diff-impl/src/com/intellij/diff/impl/DiffSettingsHolder.kt @@ -1,6 +1,7 @@ // 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.diff.impl +import com.intellij.diff.tools.fragmented.UnifiedDiffTool import com.intellij.diff.util.DiffPlaces import com.intellij.diff.util.DiffUtil import com.intellij.openapi.components.PersistentStateComponent @@ -61,7 +62,11 @@ class DiffSettingsHolder : PersistentStateComponent { } private fun defaultPlaceSettings(place: String): PlaceSettings { - return PlaceSettings() + val settings = PlaceSettings() + if (place == DiffPlaces.VCS_LOG_VIEW) { + settings.DIFF_TOOLS_ORDER = listOf(UnifiedDiffTool::class.java.canonicalName) + } + return settings } diff --git a/platform/diff-impl/src/com/intellij/diff/tools/util/base/TextDiffSettingsHolder.kt b/platform/diff-impl/src/com/intellij/diff/tools/util/base/TextDiffSettingsHolder.kt index 73c998ac23a3..80ba7aa374dc 100644 --- a/platform/diff-impl/src/com/intellij/diff/tools/util/base/TextDiffSettingsHolder.kt +++ b/platform/diff-impl/src/com/intellij/diff/tools/util/base/TextDiffSettingsHolder.kt @@ -163,6 +163,9 @@ class TextDiffSettingsHolder : PersistentStateComponent getSelectedChanges() { diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/CommitChangeListDialog.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/CommitChangeListDialog.java index 28a01af554ee..1bb56a065d5f 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/CommitChangeListDialog.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/CommitChangeListDialog.java @@ -4,7 +4,6 @@ package com.intellij.openapi.vcs.changes.ui; import com.intellij.CommonBundle; import com.intellij.diff.util.DiffPlaces; import com.intellij.diff.util.DiffUserDataKeysEx; -import com.intellij.diff.util.DiffUtil; import com.intellij.ide.HelpIdProvider; import com.intellij.ide.ui.UISettings; import com.intellij.ide.util.PropertiesComponent; @@ -1199,11 +1198,6 @@ public class CommitChangeListDialog extends DialogWrapper implements CheckinProj putContextUserData(LocalChangeListDiffTool.ALLOW_EXCLUDE_FROM_COMMIT, enablePartialCommit); } - @Override - public boolean isWindowFocused() { - return DiffUtil.isFocusedComponent(getProject(), getComponent()); - } - @NotNull @Override protected List getSelectedChanges() { diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/frame/MainFrame.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/frame/MainFrame.java index a4cb7137c98a..36ec984f2fca 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/frame/MainFrame.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/frame/MainFrame.java @@ -4,12 +4,17 @@ import com.google.common.primitives.Ints; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.actionSystem.ex.ActionUtil; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.keymap.KeymapUtil; import com.intellij.openapi.progress.util.ProgressWindow; +import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Splitter; import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.vcs.VcsConfiguration; import com.intellij.openapi.vcs.VcsDataKeys; import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.changes.PreviewDiffSplitterComponent; +import com.intellij.openapi.vcs.changes.actions.ShowDiffPreviewAction; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.OnePixelSplitter; import com.intellij.ui.PopupHandler; @@ -57,11 +62,13 @@ import static com.intellij.util.ObjectUtils.chooseNotNull; public class MainFrame extends JPanel implements DataProvider, Disposable { private static final String HELP_ID = "reference.changesToolWindow.log"; + private static final String PREVIEW_DIFF_SPLITTER_PROPORTION = "LogMainFrame.DIFF_DETAILS_SPLITTER_PROPORTION"; @NotNull private final VcsLogData myLogData; @NotNull private final AbstractVcsLogUi myUi; @NotNull private final VcsLog myLog; @NotNull private final VcsLogClassicFilterUi myFilterUi; + @NotNull private final VcsConfiguration myVcsConfiguration; @NotNull private final JBLoadingPanel myChangesLoadingPane; @NotNull private final VcsLogGraphTable myGraphTable; @@ -70,6 +77,7 @@ public class MainFrame extends JPanel implements DataProvider, Disposable { @NotNull private final JComponent myToolbar; @NotNull private final VcsLogChangesBrowser myChangesBrowser; @NotNull private final Splitter myChangesBrowserSplitter; + @NotNull private final PreviewDiffSplitterComponent myPreviewDiffSplitter; @NotNull private final SearchTextField myTextFilter; @NotNull private final MainVcsLogUiProperties myUiProperties; private final MyCommitSelectionListenerForDiff mySelectionListenerForDiff; @@ -85,6 +93,9 @@ public class MainFrame extends JPanel implements DataProvider, Disposable { myLog = log; myUiProperties = uiProperties; + Project project = logData.getProject(); + myVcsConfiguration = VcsConfiguration.getInstance(project); + myFilterUi = new VcsLogClassicFilterUi(ui, logData, myUiProperties, initialDataPack); // initialize components @@ -99,11 +110,12 @@ public class MainFrame extends JPanel implements DataProvider, Disposable { } }; - myChangesBrowser = new VcsLogChangesBrowser(logData.getProject(), myUiProperties, (commitId) -> { + myChangesBrowser = new VcsLogChangesBrowser(project, myUiProperties, (commitId) -> { int index = myLogData.getCommitIndex(commitId.getHash(), commitId.getRoot()); return myLogData.getMiniDetailsGetter().getCommitData(index, Collections.singleton(index)); }, this); myChangesBrowser.getDiffAction().registerCustomShortcutSet(myChangesBrowser.getDiffAction().getShortcutSet(), getGraphTable()); + myChangesBrowser.addToolbarAction(new MyToggleDetailsAction()); myChangesLoadingPane = new JBLoadingPanel(new BorderLayout(), this, ProgressWindow.DEFAULT_PROGRESS_DIALOG_POSTPONE_TIME_MILLIS); myChangesLoadingPane.add(myChangesBrowser); @@ -111,6 +123,21 @@ public class MainFrame extends JPanel implements DataProvider, Disposable { myDetailsSplitter.setFirstComponent(myChangesLoadingPane); setupDetailsSplitter(myUiProperties.get(CommonUiProperties.SHOW_DETAILS)); + + VcsLogChangeProcessor changeProcessor = new VcsLogChangeProcessor(project, myChangesBrowser, this); + myPreviewDiffSplitter = new PreviewDiffSplitterComponent(myDetailsSplitter, changeProcessor, PREVIEW_DIFF_SPLITTER_PROPORTION, + myVcsConfiguration.VCS_LOG_DETAILS_PREVIEW_SHOWN); + + Runnable changesListener = () -> { + ApplicationManager.getApplication().invokeLater(() -> { + // We do not have local changes here, so it's OK to always use `fromModelRefresh == false` + myPreviewDiffSplitter.updatePreview(false); + }); + }; + myChangesBrowser.getViewer().addSelectionListener(changesListener); + myChangesBrowser.setModelUpdateListener(changesListener); + + mySelectionListenerForDiff = new MyCommitSelectionListenerForDiff(); myGraphTable.getSelectionModel().addListSelectionListener(mySelectionListenerForDiff); myDetailsPanel.installCommitSelectionListener(myGraphTable); @@ -129,7 +156,7 @@ public class MainFrame extends JPanel implements DataProvider, Disposable { myChangesBrowserSplitter = new OnePixelSplitter(false, "vcs.log.changes.splitter.proportion", 0.7f); myChangesBrowserSplitter.setFirstComponent(toolbarsAndTable); - myChangesBrowserSplitter.setSecondComponent(myDetailsSplitter); + myChangesBrowserSplitter.setSecondComponent(myPreviewDiffSplitter); setLayout(new BorderLayout()); add(myChangesBrowserSplitter); @@ -220,7 +247,7 @@ public class MainFrame extends JPanel implements DataProvider, Disposable { @Override public Object getData(@NonNls String dataId) { if (VcsDataKeys.CHANGES.is(dataId) || VcsDataKeys.SELECTED_CHANGES.is(dataId)) { - return ArrayUtil.toObjectArray(myChangesBrowser.getAllChanges(), Change.class); + return ArrayUtil.toObjectArray(myChangesBrowser.getDirectChanges(), Change.class); } else if (VcsDataKeys.CHANGE_LISTS.is(dataId)) { List details = myLog.getSelectedDetails(); @@ -343,4 +370,17 @@ public class MainFrame extends JPanel implements DataProvider, Disposable { } } } + + private class MyToggleDetailsAction extends ShowDiffPreviewAction { + @Override + public void setSelected(AnActionEvent e, boolean state) { + myPreviewDiffSplitter.setDetailsOn(state); + myVcsConfiguration.VCS_LOG_DETAILS_PREVIEW_SHOWN = state; + } + + @Override + public boolean isSelected(AnActionEvent e) { + return myVcsConfiguration.VCS_LOG_DETAILS_PREVIEW_SHOWN; + } + } } diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/frame/VcsLogChangeProcessor.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/frame/VcsLogChangeProcessor.java new file mode 100644 index 000000000000..952afb5177de --- /dev/null +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/frame/VcsLogChangeProcessor.java @@ -0,0 +1,74 @@ +// 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.ui.frame; + +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.openapi.vcs.changes.ui.ChangesTree; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.ui.tree.TreeUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.TreePath; +import java.util.List; + +class VcsLogChangeProcessor extends ChangeViewDiffRequestProcessor { + @NotNull private final VcsLogChangesBrowser myBrowser; + + public VcsLogChangeProcessor(@NotNull Project project, @NotNull VcsLogChangesBrowser browser, @NotNull Disposable disposable) { + super(project, DiffPlaces.VCS_LOG_VIEW); + myBrowser = browser; + Disposer.register(disposable, this); + } + + @NotNull + @Override + protected List getSelectedChanges() { + List changes = myBrowser.getSelectedChanges(); + if (changes.isEmpty()) changes = myBrowser.getAllChanges(); + return ContainerUtil.map(changes, MyChangeWrapper::new); + } + + @NotNull + @Override + protected List getAllChanges() { + return ContainerUtil.map(myBrowser.getAllChanges(), MyChangeWrapper::new); + } + + @Override + protected void selectChange(@NotNull Wrapper change) { + ChangesTree tree = myBrowser.getViewer(); + DefaultMutableTreeNode root = (DefaultMutableTreeNode)tree.getModel().getRoot(); + DefaultMutableTreeNode objectNode = TreeUtil.findNodeWithObject(root, change.getUserObject()); + TreePath path = objectNode != null ? TreeUtil.getPathFromRoot(objectNode) : null; + if (path != null) { + TreeUtil.selectPath(tree, path, false); + } + } + + private class MyChangeWrapper extends Wrapper { + @NotNull private final Change myChange; + + public MyChangeWrapper(@NotNull Change change) { + myChange = change; + } + + @NotNull + @Override + public Object getUserObject() { + return myChange; + } + + @Nullable + @Override + public DiffRequestProducer createProducer(@Nullable Project project) { + return myBrowser.getDiffRequestProducer(myChange); + } + } +} 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 71e8f1eae2b5..4a9f854dfce3 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 @@ -61,6 +61,7 @@ class VcsLogChangesBrowser extends ChangesBrowserBase implements Disposable { @NotNull private final List myChanges = ContainerUtil.newArrayList(); @NotNull private final Map> myChangesToParents = ContainerUtil.newHashMap(); @NotNull private final Wrapper myToolbarWrapper; + @Nullable private Runnable myModelUpdateListener; public VcsLogChangesBrowser(@NotNull Project project, @NotNull MainVcsLogUiProperties uiProperties, @@ -102,6 +103,10 @@ class VcsLogChangesBrowser extends ChangesBrowserBase implements Disposable { myToolbarWrapper.setVerticalSizeReferent(referent); } + public void setModelUpdateListener(@Nullable Runnable runnable) { + myModelUpdateListener = runnable; + } + @Override public void dispose() { myUiProperties.removeChangeListener(myListener); @@ -131,6 +136,7 @@ class VcsLogChangesBrowser extends ChangesBrowserBase implements Disposable { myRoots.clear(); myViewer.setEmptyText(""); myViewer.rebuildTree(); + if (myModelUpdateListener != null) myModelUpdateListener.run(); } public void setSelectedDetails(@NotNull List detailsList) { @@ -175,6 +181,7 @@ class VcsLogChangesBrowser extends ChangesBrowserBase implements Disposable { } myViewer.rebuildTree(); + if (myModelUpdateListener != null) myModelUpdateListener.run(); } @NotNull @@ -204,10 +211,20 @@ class VcsLogChangesBrowser extends ChangesBrowserBase implements Disposable { } @NotNull - public List getAllChanges() { + public List getDirectChanges() { return myChanges; } + @NotNull + public List getSelectedChanges() { + return VcsTreeModelData.selected(myViewer).userObjects(Change.class); + } + + @NotNull + public List getAllChanges() { + return VcsTreeModelData.all(myViewer).userObjects(Change.class); + } + @Nullable @Override public Object getData(@NotNull String dataId) { @@ -224,8 +241,7 @@ class VcsLogChangesBrowser extends ChangesBrowserBase implements Disposable { List allVcs = ContainerUtil.mapNotNull(myRoots, root -> ProjectLevelVcsManager.getInstance(myProject).getVcsFor(root)); if (allVcs.size() == 1) return notNull(getFirstItem(allVcs)); - List selectedChanges = VcsTreeModelData.selected(myViewer).userObjects(Change.class); - Set selectedVcs = ChangesUtil.getAffectedVcses(selectedChanges, myProject); + Set selectedVcs = ChangesUtil.getAffectedVcses(getSelectedChanges(), myProject); if (selectedVcs.size() == 1) return notNull(getFirstItem(selectedVcs)); return null; @@ -233,7 +249,7 @@ class VcsLogChangesBrowser extends ChangesBrowserBase implements Disposable { @Nullable @Override - protected ChangeDiffRequestChain.Producer getDiffRequestProducer(@NotNull Object userObject) { + public ChangeDiffRequestChain.Producer getDiffRequestProducer(@NotNull Object userObject) { if (userObject instanceof MergedChange) { MergedChange mergedChange = (MergedChange)userObject; if (mergedChange.getSourceChanges().size() == 2) {