blob: d24a6c06442fa1a67b3d1e39ca72bdc6571f318d [file] [log] [blame]
/*******************************************************************************
* Copyright (C) 2018, 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.components;
import java.lang.reflect.InvocationTargetException;
import java.text.MessageFormat;
import java.util.Collection;
import java.util.function.Function;
import java.util.function.Supplier;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.egit.ui.UIUtils.ExplicitContentProposalAdapter;
import org.eclipse.egit.ui.UIUtils.IContentProposalCandidateProvider;
import org.eclipse.egit.ui.internal.UIText;
import org.eclipse.egit.ui.internal.dialogs.CancelableFuture;
import org.eclipse.egit.ui.internal.dialogs.NonBlockingWizardDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.wizard.IWizardContainer;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.progress.WorkbenchJob;
/**
* An {@link IContentProposalCandidateProvider} that is intended to be used with
* an asynchronous {@link CancelableFuture} to get proposals in the background.
* <p>
* Note that is must be used with an {@link ExplicitContentProposalAdapter},
* which must be made known to it via
* {@link #setContentProposalAdapter(ExplicitContentProposalAdapter)
* setContentProposalAdapter()}.
*/
public class AsynchronousRefProposalProvider
implements IContentProposalCandidateProvider<Ref> {
private final IWizardContainer container;
private final Text textField;
private final Supplier<String> uriProvider;
private final Function<String, CancelableFuture<Collection<Ref>>> listProvider;
private ExplicitContentProposalAdapter contentProposer;
/**
* Creates a new {@link AsynchronousRefProposalProvider}. Because this is
* supposed to run truly asynchronously, typically in a
* {@link NonBlockingWizardDialog}, it needs to know the text field it
* belongs to and also the URI it was run for. It opens the proposals only
* if both are in a state where it still makes sense to show the proposals.
*
* @param container
* this candidate provider will be run for
* @param uriProvider
* a function returning the upstream URI to get proposals from
* @param textField
* this candidate provider belongs to
* @param listProvider
* a function that provides the CancelableFuture used to obtain
* the upstream refs
*/
public AsynchronousRefProposalProvider(
IWizardContainer container, Text textField,
Supplier<String> uriProvider,
Function<String, CancelableFuture<Collection<Ref>>> listProvider) {
this.container = container;
this.textField = textField;
this.uriProvider = uriProvider;
this.listProvider = listProvider;
}
/**
* Makes the content proposal adapter known to this candidate provider. This
* is needed to be able to open the proposal popup asynchronously. If set to
* {@code null}, proposals will not be opened.
*
* @param adapter
* to set
*/
public void setContentProposalAdapter(
ExplicitContentProposalAdapter adapter) {
contentProposer = adapter;
}
@Override
public Collection<? extends Ref> getCandidates() {
String uri = uriProvider.get();
if (uri == null) {
return null;
}
CancelableFuture<Collection<Ref>> list = listProvider.apply(uri);
try {
if (!list.isFinished()) {
IRunnableWithProgress operation = monitor -> {
monitor.beginTask(MessageFormat.format(
UIText.AsynchronousRefProposalProvider_FetchingRemoteRefsMessage,
uri), IProgressMonitor.UNKNOWN);
Collection<Ref> result = list.get();
if (monitor.isCanceled()) {
return;
}
// If we get here, the ChangeList future is done.
if (result == null || result.isEmpty()) {
// Don't bother if we didn't get any results
return;
}
// If we do have results now, open the proposals.
Job showProposals = new WorkbenchJob(
UIText.AsynchronousRefProposalProvider_ShowingProposalsJobName) {
@Override
public boolean shouldRun() {
return super.shouldRun() && contentProposer != null;
}
@Override
public IStatus runInUIThread(
IProgressMonitor uiMonitor) {
// But only if we're not disposed, the focus is
// still (or again) in the Change field, and the uri
// is still the same
try {
if (container instanceof NonBlockingWizardDialog) {
// Otherwise the dialog was blocked anyway,
// and focus will be restored
if (textField.isDisposed()
|| !textField.isVisible()
|| textField != textField
.getDisplay()
.getFocusControl()) {
return Status.CANCEL_STATUS;
}
String uriNow = uriProvider.get();
if (!uriNow.equals(uri)) {
return Status.CANCEL_STATUS;
}
}
contentProposer.openProposalPopup();
} catch (SWTException e) {
// Disposed already
return Status.CANCEL_STATUS;
} finally {
uiMonitor.done();
}
return Status.OK_STATUS;
}
};
showProposals.schedule();
};
if (container instanceof NonBlockingWizardDialog) {
NonBlockingWizardDialog dialog = (NonBlockingWizardDialog) container;
dialog.run(operation, () -> list
.cancel(CancelableFuture.CancelMode.ABANDON));
} else {
container.run(true, true, operation);
}
return null;
}
return list.get();
} catch (InterruptedException | InvocationTargetException e) {
return null;
}
}
}