blob: 62edecf688628febe75d8143222a866fe0da7da8 [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.openapi.vcs.changes;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.diff.DiffBundle;
import com.intellij.openapi.diff.DiffContent;
import com.intellij.openapi.diff.DiffManager;
import com.intellij.openapi.diff.DiffPanel;
import com.intellij.openapi.diff.ex.DiffPanelEx;
import com.intellij.openapi.diff.ex.DiffPanelOptions;
import com.intellij.openapi.diff.impl.DiffPanelImpl;
import com.intellij.openapi.diff.impl.DiffUtil;
import com.intellij.openapi.diff.impl.external.BinaryDiffTool;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.progress.BackgroundTaskQueue;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.changes.actions.ShowDiffAction;
import com.intellij.openapi.vcs.history.ShortVcsRevisionNumber;
import com.intellij.openapi.vcs.history.VcsRevisionNumber;
import com.intellij.util.BeforeAfter;
import com.intellij.vcsUtil.UIVcsUtil;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* todo extract interface and create stub for dummy project
* @author irengrig
* Date: 6/15/11
* Time: 5:36 PM
*/
public class VcsChangeDetailsManager {
private final List<VcsChangeDetailsProvider> myProviders = new ArrayList<VcsChangeDetailsProvider>();
private final List<VcsChangeDetailsProvider> myDedicatedList;
private final Project myProject;
private final BackgroundTaskQueue myQueue;
public VcsChangeDetailsManager(final Project project) {
myProject = project;
myQueue = new BackgroundTaskQueue(myProject, "Loading change details");
myDedicatedList = new ArrayList<VcsChangeDetailsProvider>();
myDedicatedList.add(new BinaryDetailsProviderNew(project));
myDedicatedList.add(new FragmentedDiffDetailsProvider(myProject));
VcsChangeDetailsProvider[] extensions = Extensions.getExtensions(VcsChangeDetailsProvider.EP_NAME, myProject);
myProviders.addAll(Arrays.asList(extensions));
Disposer.register(project, new Disposable() {
@Override
public void dispose() {
myQueue.clear();
}
});
}
public boolean canComment(final Change change) {
for (VcsChangeDetailsProvider provider : myDedicatedList) {
if (provider.canComment(change)) return true;
}
for (VcsChangeDetailsProvider provider : myProviders) {
if (provider.canComment(change)) return true;
}
return false;
}
@Nullable
public RefreshablePanel getPanel(final Change change, JComponent parent) {
final List<Pair<String, RefreshablePanel>> panels = new ArrayList<Pair<String, RefreshablePanel>>();
for (VcsChangeDetailsProvider convertor : myDedicatedList) {
if (! convertor.canComment(change)) continue;
RefreshablePanel panel = convertor.comment(change, parent, myQueue);
if (panel != null) {
panels.add(Pair.create("Diff", panel));
break; // only one of dedicated for now
}
}
for (VcsChangeDetailsProvider provider : myProviders) {
if (provider.canComment(change)) {
RefreshablePanel panel = provider.comment(change, parent, myQueue);
if (panel != null) {
panels.add(Pair.create(provider.getName(), panel));
}
}
}
if (! panels.isEmpty()) {
if (panels.size() == 1) {
return panels.get(0).getSecond();
} else {
TabbedRefreshablePanel tabbedRefreshablePanel = new TabbedRefreshablePanel();
for (Pair<String, RefreshablePanel> panel : panels) {
tabbedRefreshablePanel.addTab(panel.getFirst(), panel.getSecond());
}
return tabbedRefreshablePanel;
}
}
return null;
}
public static VcsChangeDetailsManager getInstance(Project project) {
return ServiceManager.getService(project, VcsChangeDetailsManager.class);
}
private static class BinaryDetailsProviderNew implements VcsChangeDetailsProvider {
private final Project myProject;
private BinaryDetailsProviderNew(Project project) {
myProject = project;
}
@Override
public String getName() {
return "Contents Diff";
}
@Override
public boolean canComment(Change change) {
return canBeShownInBinaryDiff(change, myProject);
}
@Override
public RefreshablePanel comment(Change change, JComponent parent, BackgroundTaskQueue queue) {
return new BinaryDiffDetailsPanel(myProject, queue, change);
}
}
private static boolean canBeShownInBinaryDiff(Change change, final Project project) {
FilePath path = ChangesUtil.getFilePath(change);
if (path != null && path.isDirectory()) return false;
if (change.isTreeConflict() || change.isPhantom()) return false;
return ShowDiffAction.isBinaryChangeAndCanShow(project, change);
}
private static class BinaryDiffDetailsPanel extends AbstractRefreshablePanel<ValueWithVcsException<List<BeforeAfter<DiffContent>>>> {
private final BinaryDiffRequestFromChange myRequestFromChange;
private final Project myProject;
private final ChangeListManager myChangeListManager;
private final FilePath myFilePath;
private Change myChange;
private final DiffPanel myPanel;
private BinaryDiffDetailsPanel(Project project, BackgroundTaskQueue queue, final Change change) {
super(project, "Loading change content", queue);
myProject = project;
myFilePath = ChangesUtil.getFilePath(change);
myRequestFromChange = new BinaryDiffRequestFromChange(myProject);
myChangeListManager = ChangeListManager.getInstance(myProject);
myPanel = DiffManager.getInstance().createDiffPanel(null, myProject, this, BinaryDiffTool.INSTANCE);
myPanel.enableToolbar(false);
myPanel.removeStatusBar();
DiffPanelOptions o = ((DiffPanelEx)myPanel).getOptions();
o.setRequestFocusOnNewContent(false);
}
@Override
public boolean isStillValid(Change data) {
return canBeShownInBinaryDiff(data, myProject);
}
@Override
protected void refreshPresentation() {
}
@Override
protected ValueWithVcsException<List<BeforeAfter<DiffContent>>> loadImpl() throws VcsException {
myChange = myChangeListManager.getChange(myFilePath);
if (myChange == null) {
return null;
}
return new ValueWithVcsException<List<BeforeAfter<DiffContent>>>() {
@Override
protected List<BeforeAfter<DiffContent>> computeImpl() throws VcsException {
return myRequestFromChange.createRequestForChange(myChange, 0);
}
};
}
@Override
protected JPanel dataToPresentation(ValueWithVcsException<List<BeforeAfter<DiffContent>>> value) {
if (value == null) return noDifferences();
final List<BeforeAfter<DiffContent>> contents;
try {
contents = value.get();
if (contents == null) throw new VcsException("Can not load content");
if (isUnknownType(contents)) {
return UIVcsUtil.errorPanel(DiffBundle.message("diff.can.not.show.unknown"), false);
}
if (isEmpty(contents)) throw new VcsException("Can not load content");
}
catch (VcsException e) {
return UIVcsUtil.errorPanel(e.getMessage(), true);
}
if (contents.isEmpty()) return noDifferences();
assert contents.size() == 1;
myPanel.setContents(contents.get(0).getBefore(), contents.get(0).getAfter());
((DiffPanelImpl)myPanel).getOptions().setRequestFocusOnNewContent(false);
final JPanel wholeWrapper = new JPanel(new BorderLayout());
final JPanel topPanel = new JPanel(new BorderLayout());
final JPanel wrapper = new JPanel();
final BoxLayout boxLayout = new BoxLayout(wrapper, BoxLayout.X_AXIS);
wrapper.setLayout(boxLayout);
final JLabel label = new JLabel(changeDescription(myChange));
label.setBorder(BorderFactory.createEmptyBorder(1,2,0,0));
wrapper.add(label);
topPanel.add(wrapper, BorderLayout.CENTER);
wholeWrapper.add(topPanel, BorderLayout.NORTH);
//wholeWrapper.add(new JBScrollPane(panel.getComponent()), BorderLayout.CENTER);
wholeWrapper.add(myPanel.getComponent(), BorderLayout.CENTER);
return wholeWrapper;
}
private static boolean isUnknownType(List<BeforeAfter<DiffContent>> contents) {
for (BeforeAfter<DiffContent> content : contents) {
if (DiffUtil.oneIsUnknown(content.getBefore(), content.getAfter())) {
return true;
}
}
return false;
}
private boolean isEmpty(List<BeforeAfter<DiffContent>> contents) {
for (BeforeAfter<DiffContent> content : contents) {
if (! contentIsEmpty(content.getAfter()) || ! contentIsEmpty(content.getBefore())) return false;
}
return true;
}
private boolean contentIsEmpty(final DiffContent content) {
return content != null && content.isEmpty();
}
@Override
protected void disposeImpl() {
}
@Override
public void away() {
//
}
}
private static JPanel noDifferences() {
return UIVcsUtil.errorPanel(DiffBundle.message("diff.contents.have.differences.only.in.line.separators.or.whitespaces"), false);
}
private static class FragmentedDiffDetailsProvider implements VcsChangeDetailsProvider {
private final Project myProject;
private FragmentedDiffDetailsProvider(Project project) {
myProject = project;
}
@Override
public String getName() {
return "Contents Diff";
}
@Override
public boolean canComment(Change change) {
return FragmentedDiffRequestFromChange.canCreateRequest(change);
}
@Override
public RefreshablePanel comment(Change change, JComponent parent, BackgroundTaskQueue queue) {
return new FragmentedDiffDetailsPanel(myProject, queue, change, parent);
}
}
private static class FragmentedDiffDetailsPanel extends AbstractRefreshablePanel<ValueWithVcsException<PreparedFragmentedContent>> {
private final FragmentedDiffRequestFromChange myRequestFromChange;
private final FilePath myFilePath;
private final ChangeListManager myChangeListManager;
private final ChangesFragmentedDiffPanel myDiffPanel;
private final Project myProject;
private FragmentedDiffDetailsPanel(Project project, BackgroundTaskQueue queue, final Change change, JComponent parent) {
super(project, "Loading change content", queue);
myProject = project;
myFilePath = ChangesUtil.getFilePath(change);
myRequestFromChange = new FragmentedDiffRequestFromChange(project);
myChangeListManager = ChangeListManager.getInstance(project);
myDiffPanel = new ChangesFragmentedDiffPanel(project, changeDescription(change), parent);
myDiffPanel.buildUi();
}
@Override
protected void refreshPresentation() {
myDiffPanel.refreshPresentation();
}
@Override
public boolean isStillValid(Change data) {
return FragmentedDiffRequestFromChange.canCreateRequest(data);
}
@Override
protected ValueWithVcsException<PreparedFragmentedContent> loadImpl() throws VcsException {
return new ValueWithVcsException<PreparedFragmentedContent>() {
@Override
protected PreparedFragmentedContent computeImpl() throws VcsException {
final Change change = myChangeListManager.getChange(myFilePath);
if (change == null) {
return null;
}
myDiffPanel.setTitle(changeDescription(change));
return myRequestFromChange.getRanges(change);
}
};
}
@Override
protected JPanel dataToPresentation(ValueWithVcsException<PreparedFragmentedContent> value) {
final PreparedFragmentedContent requestForChange;
try {
if (value == null) return noDifferences();
requestForChange = value.get();
if (requestForChange == null) return noDifferences();
if (requestForChange.isEmpty()) {
return noDifferences();
}
}
catch (VcsException e) {
return UIVcsUtil.errorPanel(e.getMessage(), true);
}
myDiffPanel.refreshData(requestForChange);
return myDiffPanel.getPanel();
}
@Override
protected void disposeImpl() {
Disposer.dispose(myDiffPanel);
}
@Override
public boolean refreshDataSynch() {
try {
myTicket.increment();
ValueWithVcsException<PreparedFragmentedContent> exception = loadImpl();
dataToPresentation(exception);
}
catch (VcsException e) {
return false;
}
return true;
}
@Override
public void away() {
myDiffPanel.away();
}
}
private static String changeDescription(Change o) {
return ChangesUtil.getFilePath(o).getName() + " (" + (o.getBeforeRevision() == null ? "New" : beforeRevisionText(o)) + ")";
}
private static String beforeRevisionText(Change o) {
VcsRevisionNumber revisionNumber = o.getBeforeRevision().getRevisionNumber();
return revisionNumber instanceof ShortVcsRevisionNumber ? ((ShortVcsRevisionNumber) revisionNumber).toShortString() :
revisionNumber.asString();
}
}