IDEA-170988 vcs: add diff preview to Vcs Log commit details

This commit is contained in:
Aleksey Pivovarov
2018-05-09 19:31:33 +03:00
committed by Julia Beliaeva
parent 7bdbc2e630
commit 0cbffa8f2a
10 changed files with 179 additions and 41 deletions
@@ -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<DiffSettingsHolder.State> {
}
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
}
@@ -163,6 +163,9 @@ class TextDiffSettingsHolder : PersistentStateComponent<TextDiffSettingsHolder.S
if (place == DiffPlaces.COMMIT_DIALOG) {
settings.EXPAND_BY_DEFAULT = false
}
if (place == DiffPlaces.VCS_LOG_VIEW) {
settings.EXPAND_BY_DEFAULT = false
}
return settings
}
@@ -18,6 +18,7 @@ package com.intellij.diff.util;
public interface DiffPlaces {
String DEFAULT = "Default";
String CHANGES_VIEW = "ChangesView";
String VCS_LOG_VIEW = "VcsLogView";
String COMMIT_DIALOG = "CommitDialog";
String TESTS_FAILED_ASSERTIONS = "TestsFiledAssertions";
String MERGE = "Merge";
@@ -69,6 +69,7 @@ public final class VcsConfiguration implements PersistentStateComponent<VcsConfi
public boolean SHOW_UNVERSIONED_FILES_WHILE_COMMIT = true;
public boolean LOCAL_CHANGES_DETAILS_PREVIEW_SHOWN = false;
public boolean SHELVE_DETAILS_PREVIEW_SHOWN = false;
public boolean VCS_LOG_DETAILS_PREVIEW_SHOWN = false;
public boolean RELOAD_CONTEXT = true;
@XCollection(elementName = "path", propertyElementName = "ignored-roots")
@@ -26,10 +26,12 @@ import com.intellij.diff.requests.ErrorDiffRequest;
import com.intellij.diff.requests.LoadingDiffRequest;
import com.intellij.diff.tools.util.PrevNextDifferenceIterable;
import com.intellij.diff.util.DiffUserDataKeysEx.ScrollToPolicy;
import com.intellij.diff.util.DiffUtil;
import com.intellij.openapi.diff.DiffBundle;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.UserDataHolder;
import com.intellij.openapi.vcs.changes.actions.diff.ChangeDiffRequestProducer;
import com.intellij.openapi.vcs.changes.actions.diff.UnversionedDiffRequestProducer;
@@ -116,6 +118,11 @@ public abstract class ChangeViewDiffRequestProcessor extends CacheDiffRequestPro
return super.getProject();
}
@Override
public boolean isWindowFocused() {
return DiffUtil.isFocusedComponent(getProject(), getComponent());
}
//
// Navigation
//
@@ -157,18 +164,21 @@ public abstract class ChangeViewDiffRequestProcessor extends CacheDiffRequestPro
}
if (selectedChanges.isEmpty()) {
myCurrentChange = null;
updateRequest();
setCurrentChange(null);
return;
}
if (selectedChange == null) {
myCurrentChange = selectedChanges.get(0);
updateRequest();
setCurrentChange(selectedChanges.get(0));
return;
}
myCurrentChange = selectedChange;
setCurrentChange(selectedChange);
}
@CalledInAwt
public void setCurrentChange(@Nullable Wrapper change) {
myCurrentChange = change;
updateRequest();
}
@@ -274,6 +284,20 @@ public abstract class ChangeViewDiffRequestProcessor extends CacheDiffRequestPro
@Nullable
public abstract DiffRequestProducer createProducer(@Nullable Project project);
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (getClass() != o.getClass()) return false;
Wrapper wrapper = (Wrapper)o;
return Comparing.equal(getUserObject(), wrapper.getUserObject());
}
@Override
public int hashCode() {
return getUserObject().hashCode();
}
}
protected static class ChangeWrapper extends Wrapper {
@@ -286,7 +310,7 @@ public abstract class ChangeViewDiffRequestProcessor extends CacheDiffRequestPro
@NotNull
@Override
public Object getUserObject() {
return this.change;
return change;
}
@Nullable
@@ -332,7 +356,7 @@ public abstract class ChangeViewDiffRequestProcessor extends CacheDiffRequestPro
@NotNull
@Override
public Object getUserObject() {
return this.file;
return file;
}
@Nullable
@@ -340,20 +364,6 @@ public abstract class ChangeViewDiffRequestProcessor extends CacheDiffRequestPro
public DiffRequestProducer createProducer(@Nullable Project project) {
return UnversionedDiffRequestProducer.create(project, file);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (getClass() != o.getClass()) return false;
UnversionedFileWrapper wrapper = (UnversionedFileWrapper)o;
return wrapper.file.equals(file);
}
@Override
public int hashCode() {
return file.hashCode();
}
}
private static class ErrorChangeRequestProducer implements DiffRequestProducer {
@@ -3,7 +3,6 @@
package com.intellij.openapi.vcs.changes;
import com.intellij.diff.util.DiffPlaces;
import com.intellij.diff.util.DiffUtil;
import com.intellij.icons.AllIcons;
import com.intellij.ide.CommonActionsManager;
import com.intellij.ide.DefaultTreeExpander;
@@ -510,11 +509,6 @@ public class ChangesViewManager implements ChangesViewI, ProjectComponent, Persi
Disposer.register(project, this);
}
@Override
public boolean isWindowFocused() {
return DiffUtil.isFocusedComponent(myProject, myContent.getComponent());
}
@NotNull
@Override
protected List<Wrapper> getSelectedChanges() {
@@ -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<Wrapper> getSelectedChanges() {
@@ -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<VcsFullCommitDetails> 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;
}
}
}
@@ -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<Wrapper> getSelectedChanges() {
List<Change> changes = myBrowser.getSelectedChanges();
if (changes.isEmpty()) changes = myBrowser.getAllChanges();
return ContainerUtil.map(changes, MyChangeWrapper::new);
}
@NotNull
@Override
protected List<Wrapper> 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);
}
}
}
@@ -61,6 +61,7 @@ class VcsLogChangesBrowser extends ChangesBrowserBase implements Disposable {
@NotNull private final List<Change> myChanges = ContainerUtil.newArrayList();
@NotNull private final Map<CommitId, Set<Change>> 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<VcsFullCommitDetails> 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<Change> getAllChanges() {
public List<Change> getDirectChanges() {
return myChanges;
}
@NotNull
public List<Change> getSelectedChanges() {
return VcsTreeModelData.selected(myViewer).userObjects(Change.class);
}
@NotNull
public List<Change> 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<AbstractVcs> allVcs = ContainerUtil.mapNotNull(myRoots, root -> ProjectLevelVcsManager.getInstance(myProject).getVcsFor(root));
if (allVcs.size() == 1) return notNull(getFirstItem(allVcs));
List<Change> selectedChanges = VcsTreeModelData.selected(myViewer).userObjects(Change.class);
Set<AbstractVcs> selectedVcs = ChangesUtil.getAffectedVcses(selectedChanges, myProject);
Set<AbstractVcs> 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) {