blob: ebd027cb17467acbc5cea3f394f21aa4365e2efe [file] [log] [blame]
/*******************************************************************************
* Copyright (C) 2008, 2012 Shawn O. Pearce <spearce@spearce.org>
* Copyright (C) 2012, Daniel Megert <daniel_megert@ch.ibm.com>
* Copyright (C) 2012, 2013 Robin Stocker <robin@nibor.org>
* Copyright (C) 2012, Gunnar Wagenknecht <gunnar@wagenknecht.org>
* Copyright (C) 2013, Laurent Goubet <laurent.goubet@obeo.fr>
* Copyright (C) 2016, 2019 Thomas Wolf <thomas.wolf@paranor.ch>
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.egit.ui.internal.history;
import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.egit.core.internal.job.JobUtil;
import org.eclipse.egit.core.internal.storage.CommitFileRevision;
import org.eclipse.egit.core.internal.util.ResourceUtil;
import org.eclipse.egit.ui.Activator;
import org.eclipse.egit.ui.JobFamilies;
import org.eclipse.egit.ui.UIPreferences;
import org.eclipse.egit.ui.UIUtils;
import org.eclipse.egit.ui.internal.ActionUtils;
import org.eclipse.egit.ui.internal.CompareUtils;
import org.eclipse.egit.ui.internal.UIIcons;
import org.eclipse.egit.ui.internal.UIText;
import org.eclipse.egit.ui.internal.blame.BlameOperation;
import org.eclipse.egit.ui.internal.commit.DiffViewer;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
import org.eclipse.jface.viewers.IOpenListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.OpenEvent;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.DiffEntry.ChangeType;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.Clipboard;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Table;
import org.eclipse.team.core.history.IFileRevision;
import org.eclipse.team.ui.history.IHistoryView;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchSite;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.part.IShowInSource;
import org.eclipse.ui.part.IShowInTarget;
import org.eclipse.ui.part.ShowInContext;
import org.eclipse.ui.progress.UIJob;
import org.eclipse.ui.themes.ColorUtil;
/**
* Viewer to display {@link FileDiff} objects in a table.
*/
public class CommitFileDiffViewer extends TableViewer {
static final int INTERESTING_MARK_TREE_FILTER_INDEX = 0;
private static int preventBug499850(int style) {
if (((style & (SWT.VIRTUAL | SWT.MULTI)) == (SWT.VIRTUAL | SWT.MULTI))
&& "gtk".equals(SWT.getPlatform()) && SWT.getVersion() < 4919) { //$NON-NLS-1$
// Work-around for bug 499850: calling Table.removeAll() on a
// virtual table with multi-selections enabled may cause the UI to
// freeze for very long on GTK. (GTK runs into an exponential
// algorithm). As of SWT version 4919 (Eclipse 4.10), SWT has a
// low-level work-around for this problem.
return style & ~SWT.MULTI;
}
return style;
}
private static final String LINESEP = System.getProperty("line.separator"); //$NON-NLS-1$
private Clipboard clipboard;
private IAction selectAll;
private IAction copy;
private IAction copyAll;
private IAction openThisVersion;
private IAction openPreviousVersion;
private IAction blame;
private IAction openWorkingTreeVersion;
private IAction compareWithPrevious;
private IAction compareWorkingTreeVersion;
private IAction showInHistory;
private FileDiffInput realInput;
private FileDiffLoader loader;
private final IWorkbenchSite site;
/**
* Shows a list of file changed by a commit. The viewer is created with the
* default styles: a virtual table with borders and scroll bars, with
* multi-selection enabled.
*
* @param parent
* @param site
*/
public CommitFileDiffViewer(final Composite parent,
final IWorkbenchSite site) {
this(parent, site, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER
| SWT.VIRTUAL | SWT.FULL_SELECTION);
}
/**
* Shows a list of file changed by a commit. The viewer is created with the
* given SWT styles and has by default an {@link ArrayContentProvider}. A
* label provider and {@link ViewerComparator} are set, and a context menu
* is set up.
*
* @param parent
* @param site
* @param style
* SWT style bits
*/
public CommitFileDiffViewer(final Composite parent,
final IWorkbenchSite site, final int style) {
super(parent, preventBug499850(style));
this.site = site;
final Table rawTable = getTable();
Color fg = rawTable.getForeground();
Color bg = rawTable.getBackground();
RGB dimmedForegroundRgb = ColorUtil.blend(fg.getRGB(), bg.getRGB(), 60);
ColumnViewerToolTipSupport.enableFor(this);
setLabelProvider(new FileDiffLabelProvider(dimmedForegroundRgb));
setContentProvider(ArrayContentProvider.getInstance());
setComparator(new ViewerComparator() {
@Override
public int compare(Viewer viewer, Object left, Object right) {
if (left instanceof FileDiff && right instanceof FileDiff) {
return FileDiff.PATH_COMPARATOR.compare((FileDiff) left,
(FileDiff) right);
}
return super.compare(viewer, left, right);
}
});
addOpenListener(new IOpenListener() {
@Override
public void open(final OpenEvent event) {
ISelection s = event.getSelection();
if (s.isEmpty() || !(s instanceof IStructuredSelection)) {
return;
}
IStructuredSelection iss = (IStructuredSelection) s;
FileDiff d = (FileDiff) iss.getFirstElement();
if (Activator.getDefault().getPreferenceStore().getBoolean(
UIPreferences.RESOURCEHISTORY_COMPARE_MODE)) {
showTwoWayFileDiff(d);
} else {
if (d.getChange() == ChangeType.DELETE) {
openPreviousVersionInEditor(d);
} else {
openThisVersionInEditor(d);
}
}
}
});
addSelectionChangedListener(new ISelectionChangedListener() {
@Override
public void selectionChanged(SelectionChangedEvent event) {
updateActionEnablement(event.getSelection());
}
});
clipboard = new Clipboard(rawTable.getDisplay());
rawTable.addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(final DisposeEvent e) {
clipboard.dispose();
}
});
final MenuManager mgr = new MenuManager();
rawTable.setMenu(mgr.createContextMenu(rawTable));
openThisVersion = new Action(
UIText.CommitFileDiffViewer_OpenInEditorMenuLabel) {
@Override
public void run() {
withSelection(
CommitFileDiffViewer.this::openThisVersionInEditor);
}
};
openPreviousVersion = new Action(
UIText.CommitFileDiffViewer_OpenPreviousInEditorMenuLabel) {
@Override
public void run() {
withSelection(
CommitFileDiffViewer.this::openPreviousVersionInEditor);
}
};
blame = new Action(UIText.CommitFileDiffViewer_ShowAnnotationsMenuLabel,
UIIcons.ANNOTATE) {
@Override
public void run() {
withSelection(CommitFileDiffViewer.this::showAnnotations);
}
};
openWorkingTreeVersion = new Action(
UIText.CommitFileDiffViewer_OpenWorkingTreeVersionInEditorMenuLabel) {
@Override
public void run() {
withSelection(d -> {
String relativePath = d.getPath();
File file = new Path(
d.getRepository().getWorkTree()
.getAbsolutePath())
.append(relativePath).toFile();
DiffViewer.openFileInEditor(file, -1);
});
}
};
compareWithPrevious = new Action(
UIText.CommitFileDiffViewer_CompareMenuLabel) {
@Override
public void run() {
withFirstSelected(
CommitFileDiffViewer.this::showTwoWayFileDiff);
}
};
compareWorkingTreeVersion = new Action(
UIText.CommitFileDiffViewer_CompareWorkingDirectoryMenuLabel) {
@Override
public void run() {
withFirstSelected(
CommitFileDiffViewer.this::showWorkingDirectoryFileDiff);
}
};
showInHistory = new Action(
UIText.CommitFileDiffViewer_ShowInHistoryLabel,
UIIcons.HISTORY) {
@Override
public void run() {
ShowInContext context = getShowInContext();
if (context == null)
return;
IWorkbenchWindow window = PlatformUI.getWorkbench()
.getActiveWorkbenchWindow();
IWorkbenchPage page = window.getActivePage();
IWorkbenchPart part = page.getActivePart();
// paranoia
if (part instanceof IHistoryView) {
((IShowInTarget) part).show(context);
}
}
};
mgr.add(openWorkingTreeVersion);
mgr.add(openThisVersion);
mgr.add(openPreviousVersion);
mgr.add(new Separator());
mgr.add(compareWithPrevious);
mgr.add(compareWorkingTreeVersion);
mgr.add(blame);
mgr.add(new Separator());
mgr.add(showInHistory);
MenuManager showInSubMenu = UIUtils
.createShowInMenu(site.getWorkbenchWindow());
mgr.add(showInSubMenu);
mgr.add(new Separator());
copy = ActionUtils.createGlobalAction(ActionFactory.COPY,
() -> {
IStructuredSelection selection = getStructuredSelection();
if (selection != null && !selection.isEmpty()) {
doCopy(selection.iterator());
}
});
copy.setText(UIText.CommitFileDiffViewer_CopyFilePathMenuLabel);
copy.setEnabled(true);
if ((rawTable.getStyle() & SWT.MULTI) != 0) {
selectAll = ActionUtils.createGlobalAction(ActionFactory.SELECT_ALL,
() -> doSelectAll());
selectAll.setEnabled(true);
ActionUtils.setGlobalActions(rawTable, copy, selectAll);
mgr.add(selectAll);
} else {
ActionUtils.setGlobalActions(rawTable, copy);
}
mgr.add(copy);
copyAll = new Action(
UIText.CommitFileDiffViewer_CopyAllFilePathsMenuLabel) {
@Override
public void run() {
doCopy(Arrays
.asList(((IStructuredContentProvider) getContentProvider())
.getElements(getInput()))
.iterator());
}
};
mgr.add(copyAll);
mgr.addMenuListener(manager -> getControl().setFocus());
}
private void withSelection(Consumer<FileDiff> consumer) {
IStructuredSelection selection = getStructuredSelection();
if (selection == null || selection.isEmpty()) {
return;
}
Iterator<?> items = selection.iterator();
items.forEachRemaining(o -> {
if (o instanceof FileDiff) {
consumer.accept((FileDiff) o);
}
});
}
private void withFirstSelected(Consumer<FileDiff> consumer) {
IStructuredSelection selection = getStructuredSelection();
if (selection == null || selection.isEmpty()) {
return;
}
Object o = selection.getFirstElement();
if (o instanceof FileDiff) {
consumer.accept((FileDiff) o);
}
}
@Override
protected void inputChanged(Object input, Object oldInput) {
super.inputChanged(input, oldInput);
copyAll.setEnabled(
getContentProvider() instanceof IStructuredContentProvider
&& doGetItemCount() > 0);
}
private void updateActionEnablement(ISelection selection) {
if (!(selection instanceof IStructuredSelection))
return;
IStructuredSelection sel = (IStructuredSelection) selection;
boolean allSelected = !sel.isEmpty()
&& sel.size() == getTable().getItemCount();
boolean submoduleSelected = false;
boolean addSelected = false;
boolean deleteSelected = false;
for (Object item : sel.toList()) {
FileDiff fileDiff = (FileDiff) item;
if (fileDiff.isSubmodule()) {
submoduleSelected = true;
}
if (fileDiff.getChange() == ChangeType.ADD) {
addSelected = true;
} else if (fileDiff.getChange() == ChangeType.DELETE) {
deleteSelected = true;
}
}
if (selectAll != null) {
selectAll.setEnabled(!allSelected);
}
copy.setEnabled(!sel.isEmpty());
showInHistory.setEnabled(!sel.isEmpty());
if (!submoduleSelected) {
boolean oneOrMoreSelected = !sel.isEmpty();
openThisVersion.setEnabled(oneOrMoreSelected && !deleteSelected);
openPreviousVersion.setEnabled(oneOrMoreSelected && !addSelected);
compareWithPrevious.setEnabled(
sel.size() == 1 && !addSelected && !deleteSelected);
blame.setEnabled(oneOrMoreSelected);
if (sel.size() == 1) {
FileDiff diff = (FileDiff) sel.getFirstElement();
Repository repo = diff.getRepository();
boolean workTreeFileExists = false;
if (!repo.isBare()) {
String path = new Path(repo.getWorkTree().getAbsolutePath())
.append(diff.getPath()).toOSString();
workTreeFileExists = new File(path).exists();
}
compareWorkingTreeVersion.setEnabled(workTreeFileExists);
openWorkingTreeVersion.setEnabled(workTreeFileExists);
} else {
compareWorkingTreeVersion.setEnabled(false);
openWorkingTreeVersion.setEnabled(oneOrMoreSelected);
}
} else {
openThisVersion.setEnabled(false);
openPreviousVersion.setEnabled(false);
openWorkingTreeVersion.setEnabled(false);
compareWithPrevious.setEnabled(false);
blame.setEnabled(false);
compareWorkingTreeVersion.setEnabled(false);
}
}
@Override
protected void handleDispose(DisposeEvent event) {
cancelJob();
realInput = null;
super.handleDispose(event);
}
/**
* A variant of
* {@link org.eclipse.jface.viewers.StructuredViewer#setInput(Object)
* setInput(Object)} that clears the selection before setting the new input
* if it is known that it cannot be reset after the input has been changed.
*
* @param input
* to set
*/
public void newInput(Object input) {
cancelJob();
if (input == null) {
setSelection(StructuredSelection.EMPTY);
setInput(new Object());
} else {
if (realInput != null) {
if (input instanceof FileDiffInput) {
FileDiffInput newInput = (FileDiffInput) input;
if (!Objects.equals(realInput.getRepository(),
newInput.getRepository())
|| !realInput.getCommit()
.equals(newInput.getCommit())) {
setSelection(StructuredSelection.EMPTY);
setInput(new Object());
}
}
}
}
if (input instanceof FileDiffInput) {
realInput = (FileDiffInput) input;
startJob((FileDiffInput) input);
} else {
realInput = null;
setInput(input);
}
}
@Override
protected void setSelectionToWidget(List list, boolean reveal) {
// setSelection(StructuredSelection.EMPTY) is not the same
// as setSelection(null). However, the latter is undocumented.
// Ensure here that we do take all possible shortcuts and just
// clear the selection (normally via doDeselectAll()) if the
// list is non-null but empty.
if (list != null && list.isEmpty()) {
list = null;
}
super.setSelectionToWidget(list, reveal);
}
/**
* @return the show in context or null
* @see IShowInSource#getShowInContext()
*/
public ShowInContext getShowInContext() {
IStructuredSelection selection = getStructuredSelection();
List<Object> elements = new ArrayList<>();
List<File> files = new ArrayList<>();
Repository repo = null;
IPath workTreePath = null;
for (Object selectedElement : selection.toList()) {
FileDiff fileDiff = (FileDiff) selectedElement;
if (repo == null || workTreePath == null) {
repo = fileDiff.getRepository();
if (repo == null || repo.isBare()) {
return null;
}
workTreePath = new Path(repo.getWorkTree().getAbsolutePath());
}
IPath path = workTreePath.append(fileDiff.getPath());
IFile file = ResourceUtil.getFileForLocation(path, false);
if (file != null)
elements.add(file);
else
elements.add(path);
files.add(path.toFile());
}
HistoryPageInput historyPageInput = null;
if (!files.isEmpty()) {
historyPageInput = new HistoryPageInput(repo,
files.toArray(new File[0]));
}
return new ShowInContext(historyPageInput,
new StructuredSelection(elements));
}
private void openThisVersionInEditor(FileDiff d) {
DiffViewer.openInEditor(d, DiffEntry.Side.NEW, -1);
}
private void openPreviousVersionInEditor(FileDiff d) {
DiffViewer.openInEditor(d, DiffEntry.Side.OLD, -1);
}
private void showAnnotations(FileDiff d) {
try {
IWorkbenchWindow window = PlatformUI.getWorkbench()
.getActiveWorkbenchWindow();
IWorkbenchPage page = window.getActivePage();
RevCommit commit = d.getChange().equals(ChangeType.DELETE)
? d.getCommit().getParent(0) : d.getCommit();
String path = d.getPath();
IFileRevision rev = CompareUtils.getFileRevision(path, commit,
d.getRepository(),
d.getChange().equals(ChangeType.DELETE) ? d.getBlobs()[0]
: d.getBlobs()[d.getBlobs().length - 1]);
if (rev instanceof CommitFileRevision) {
BlameOperation op = new BlameOperation((CommitFileRevision) rev,
window.getShell(), page);
JobUtil.scheduleUserJob(op, UIText.ShowBlameHandler_JobName,
JobFamilies.BLAME);
} else {
String message = NLS.bind(
UIText.DiffViewer_notContainedInCommit, path,
d.getCommit().getId().getName());
Activator.showError(message, null);
}
} catch (IOException e) {
Activator.logError(UIText.GitHistoryPage_openFailed, e);
Activator.showError(UIText.GitHistoryPage_openFailed, null);
}
}
void showTwoWayFileDiff(final FileDiff d) {
if (d.getBlobs().length <= 2) {
DiffViewer.showTwoWayFileDiff(d);
} else {
MessageDialog.openInformation(
PlatformUI.getWorkbench().getActiveWorkbenchWindow()
.getShell(),
UIText.CommitFileDiffViewer_CanNotOpenCompareEditorTitle,
UIText.CommitFileDiffViewer_MergeCommitMultiAncestorMessage);
}
}
void showWorkingDirectoryFileDiff(final FileDiff d) {
String p = d.getPath();
RevCommit commit = d.getCommit();
Repository repo = d.getRepository();
if (commit == null || repo == null) {
Activator.showError(UIText.GitHistoryPage_openFailed, null);
return;
}
IWorkbenchPage activePage = site.getWorkbenchWindow().getActivePage();
IFile file = ResourceUtil.getFileForLocation(repo, p, false);
try {
if (file != null) {
final IResource[] resources = new IResource[] { file, };
CompareUtils.compare(resources, repo, Constants.HEAD,
commit.getName(), true, activePage);
} else {
IPath path = new Path(repo.getWorkTree().getAbsolutePath())
.append(p);
File ioFile = path.toFile();
if (ioFile.exists()) {
CompareUtils.compare(path, repo, Constants.HEAD,
commit.getName(), true, activePage);
}
}
} catch (IOException e) {
Activator.handleError(UIText.GitHistoryPage_openFailed, e, true);
}
}
private void doSelectAll() {
if (getInput() != null) {
Table table = getTable();
if (table != null) {
table.selectAll();
}
}
}
private void doCopy(Iterator<?> items) {
final StringBuilder r = new StringBuilder();
while (items.hasNext()) {
Object obj = items.next();
if (obj instanceof FileDiff) {
if (r.length() > 0)
r.append(LINESEP);
r.append(((FileDiff) obj).getPath());
}
}
clipboard.setContents(new Object[] { r.toString() },
new Transfer[] { TextTransfer.getInstance() }, DND.CLIPBOARD);
}
/**
* Set the interesting paths to be marked and re-compute and update the UI.
*
* @param interestingPaths
* to be marked
*/
void setInterestingPaths(Collection<String> interestingPaths) {
if (realInput != null) {
cancelJob();
realInput.setInterestingPaths(interestingPaths);
startJob(realInput);
}
}
private TreeFilter toFilter(Collection<String> paths) {
if (paths != null && !paths.isEmpty()) {
return PathFilterGroup.createFromStrings(paths);
} else {
return TreeFilter.ALL;
}
}
private void startJob(FileDiffInput input) {
FileDiffLoader job = new FileDiffLoader(input,
toFilter(input.getInterestingPaths()));
job.addJobChangeListener(new JobChangeAdapter() {
@Override
public void done(IJobChangeEvent event) {
if (!event.getResult().isOK()) {
return;
}
UIJob updater = new UpdateJob(MessageFormat.format(
UIText.CommitFileDiffViewer_updatingFileDiffs,
input.getCommit().getName()), job);
updater.schedule();
}
});
job.setUser(false);
job.setSystem(true);
loader = job;
loader.schedule();
}
private void cancelJob() {
if (loader != null) {
loader.cancel();
loader = null;
}
}
private static class FileDiffLoader extends Job {
private FileDiff[] diffs;
private final FileDiffInput input;
private final TreeFilter filter;
public FileDiffLoader(FileDiffInput input, TreeFilter filter) {
super(MessageFormat.format(
UIText.CommitFileDiffViewer_computingFileDiffs,
input.getCommit().getName()));
this.input = input;
this.filter = filter;
setRule(new TreeWalkSchedulingRule(input.getTreeWalk()));
}
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
if (monitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
diffs = FileDiff.compute(input.getRepository(),
input.getTreeWalk(), input.getCommit(), monitor,
filter);
} catch (IOException err) {
Activator.handleError(MessageFormat.format(
UIText.CommitFileDiffViewer_errorGettingDifference,
input.getCommit().getId()), err, false);
}
if (monitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
return Status.OK_STATUS;
}
public FileDiff[] getDiffs() {
return diffs;
}
public FileDiffInput getInput() {
return input;
}
@Override
public boolean belongsTo(Object family) {
return family == JobFamilies.HISTORY_FILE_DIFF
|| super.belongsTo(family);
}
}
private class UpdateJob extends UIJob {
private final FileDiffLoader loadJob;
public UpdateJob(String name, FileDiffLoader loadJob) {
super(name);
this.loadJob = loadJob;
}
@Override
public IStatus runInUIThread(IProgressMonitor monitor) {
Control control = getControl();
if (control == null || control.isDisposed() || loader != loadJob) {
return Status.CANCEL_STATUS;
}
FileDiff[] diffs = loadJob.getDiffs();
try {
control.setRedraw(false);
setInput(diffs);
FileDiff interesting = getFirstInterestingElement(diffs);
if (interesting != null) {
if (loadJob.getInput().isSelectMarked()) {
setSelection(new StructuredSelection(interesting),
true);
} else {
reveal(interesting);
}
}
} finally {
control.setRedraw(true);
}
return Status.OK_STATUS;
}
private FileDiff getFirstInterestingElement(FileDiff[] diffs) {
if (diffs != null) {
for (FileDiff d : diffs) {
if (d.isMarked(INTERESTING_MARK_TREE_FILTER_INDEX)) {
return d;
}
}
}
return null;
}
}
/**
* Serializes all load jobs using the same tree walk. Tree walks are not
* thread safe.
*/
private static class TreeWalkSchedulingRule implements ISchedulingRule {
private final TreeWalk treeWalk;
public TreeWalkSchedulingRule(TreeWalk treeWalk) {
this.treeWalk = treeWalk;
}
@Override
public boolean contains(ISchedulingRule rule) {
if (rule instanceof TreeWalkSchedulingRule) {
return Objects.equals(treeWalk,
((TreeWalkSchedulingRule) rule).treeWalk);
}
return false;
}
@Override
public boolean isConflicting(ISchedulingRule rule) {
return contains(rule);
}
}
}