blob: 63bff938a26f40215bc3af5d45fcfa618e4a6715 [file] [log] [blame]
package com.intellij.tasks.actions;
import com.intellij.ide.util.gotoByName.ChooseByNameBase;
import com.intellij.ide.util.gotoByName.ChooseByNameItemProvider;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiManager;
import com.intellij.tasks.Task;
import com.intellij.tasks.TaskManager;
import com.intellij.tasks.doc.TaskPsiElement;
import com.intellij.util.Alarm;
import com.intellij.util.Processor;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;
/**
* @author Mikhail Golubev
*/
class TaskItemProvider implements ChooseByNameItemProvider, Disposable {
private static final Logger LOG = Logger.getInstance(TaskItemProvider.class);
private static final int DELAY_PERIOD = 1000; // ms
private final Project myProject;
private int myCurrentOffset = 0;
private boolean myOldEverywhere = false;
private String myOldPattern = "";
private final Alarm myAlarm = new Alarm(Alarm.ThreadToUse.POOLED_THREAD, this);
private final AtomicReference<Future<List<Task>>> myFutureReference = new AtomicReference<Future<List<Task>>>();
public TaskItemProvider(Project project) {
myProject = project;
}
@NotNull
@Override
public List<String> filterNames(@NotNull ChooseByNameBase base, @NotNull String[] names, @NotNull String pattern) {
return ContainerUtil.emptyList();
}
@Override
public boolean filterElements(@NotNull ChooseByNameBase base,
@NotNull final String pattern,
final boolean everywhere,
@NotNull final ProgressIndicator cancelled,
@NotNull Processor<Object> consumer) {
GotoTaskAction.CREATE_NEW_TASK_ACTION.setTaskName(pattern);
if (!consumer.process(GotoTaskAction.CREATE_NEW_TASK_ACTION)) return false;
TaskManager taskManager = TaskManager.getManager(myProject);
List<Task> allCachedAndLocalTasks = ContainerUtil.concat(taskManager.getCachedIssues(), taskManager.getLocalTasks());
List<Task> filteredCachedAndLocalTasks = TaskSearchSupport.getLocalAndCachedTasks(taskManager, pattern, everywhere);
if (!processTasks(filteredCachedAndLocalTasks, consumer, cancelled)) return false;
if (filteredCachedAndLocalTasks.size() >= base.getMaximumListSizeLimit()) {
return true;
}
FutureTask<List<Task>> future = new FutureTask<List<Task>>(new Callable<List<Task>>() {
@Override
public List<Task> call() throws Exception {
return fetchFromServer(pattern, everywhere, cancelled);
}
});
// Newer request always wins
Future<List<Task>> oldFeature = myFutureReference.getAndSet(future);
if (oldFeature != null) {
LOG.debug("Cancelling existing task");
oldFeature.cancel(true);
}
if (myAlarm.isDisposed()) {
return false;
}
myAlarm.addRequest(future, DELAY_PERIOD);
try {
List<Task> tasks = future.get();
myFutureReference.compareAndSet(future, null);
// Exclude *all* cached and local issues, not only those returned by TaskSearchSupport.getLocalAndCachedTasks().
// Previously used approach might lead to the following strange behavior. Local task excluded by getLocalAndCachedTasks()
// as "locally closed" (i.e. having no associated change list) was indeed *included* in popup because it
// was contained in server response (as not remotely closed). Moreover on next request with pagination when the
// same issues was not returned again by server it was *excluded* from popup (thus subsequent update reduced total
// number of items shown).
tasks.removeAll(allCachedAndLocalTasks);
return processTasks(tasks, consumer, cancelled);
}
catch (InterruptedException interrupted) {
Thread.interrupted();
}
catch (CancellationException e) {
LOG.debug("Task cancelled");
}
catch (ExecutionException e) {
Throwable cause = e.getCause();
if (cause instanceof ProcessCanceledException) {
LOG.debug("Task cancelled via progress indicator");
}
else if (cause instanceof RuntimeException) {
throw (RuntimeException)cause;
}
else if (cause instanceof Error) {
throw (Error)cause;
}
else {
throw new RuntimeException("Unknown checked exception", cause);
}
}
return false;
}
/**
* Download next page of tasks from server(s). If filtering settings changed, download them all over again, but always increment
* upper limit to make progress. If server supports search on its side, it should not affect access time much, because only a few
* tasks will be returned anyway.
*/
private List<Task> fetchFromServer(String pattern, boolean everywhere, ProgressIndicator cancelled) {
int offset, limit;
// pattern changed -> reset pagination
if (!myOldPattern.equals(pattern)) {
myCurrentOffset = offset = 0;
limit = GotoTaskAction.PAGE_SIZE;
}
// include closed tasks -> download all previous + closed, so no one is missing
else if (myOldEverywhere != everywhere) {
offset = 0;
myCurrentOffset = limit = myCurrentOffset + GotoTaskAction.PAGE_SIZE;
}
// normal pagination step
else {
offset = myCurrentOffset;
limit = GotoTaskAction.PAGE_SIZE;
myCurrentOffset += GotoTaskAction.PAGE_SIZE;
}
List<Task> tasks = TaskSearchSupport.getRepositoriesTasks(TaskManager.getManager(myProject),
pattern, offset, limit, true, everywhere, cancelled);
myOldEverywhere = everywhere;
myOldPattern = pattern;
return tasks;
}
private boolean processTasks(List<Task> tasks, Processor<Object> consumer, ProgressIndicator cancelled) {
if (!tasks.isEmpty() && !consumer.process(ChooseByNameBase.NON_PREFIX_SEPARATOR)) {
return false;
}
PsiManager psiManager = PsiManager.getInstance(myProject);
for (Task task : tasks) {
cancelled.checkCanceled();
if (!consumer.process(new TaskPsiElement(psiManager, task))) return false;
}
return true;
}
@Override
public void dispose() {
// Alarm should be disposed already
Future<List<Task>> future = myFutureReference.get();
if (future != null) {
future.cancel(true);
}
}
}