| /******************************************************************************* |
| * Copyright (c) 2004, 2011 Frank Becker and others. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0 which is available at |
| * https://www.eclipse.org/legal/epl-2.0 |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Frank Becker - initial API and implementation |
| * Tasktop Technologies - improvements |
| *******************************************************************************/ |
| |
| package org.eclipse.mylyn.internal.tasks.ui.editors; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Set; |
| |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.jface.text.ITextSelection; |
| import org.eclipse.jface.text.ITextViewer; |
| import org.eclipse.jface.text.contentassist.CompletionProposal; |
| import org.eclipse.jface.text.contentassist.ICompletionProposal; |
| import org.eclipse.jface.text.contentassist.IContentAssistProcessor; |
| import org.eclipse.jface.text.contentassist.IContextInformation; |
| import org.eclipse.jface.text.contentassist.IContextInformationValidator; |
| import org.eclipse.jface.viewers.LabelProvider; |
| import org.eclipse.mylyn.commons.ui.CommonImages; |
| import org.eclipse.mylyn.internal.tasks.core.AbstractTask; |
| import org.eclipse.mylyn.internal.tasks.core.LocalTask; |
| import org.eclipse.mylyn.internal.tasks.core.TaskActivationHistory; |
| import org.eclipse.mylyn.internal.tasks.core.TaskList; |
| import org.eclipse.mylyn.internal.tasks.ui.TasksUiPlugin; |
| import org.eclipse.mylyn.internal.tasks.ui.actions.CopyTaskDetailsAction; |
| import org.eclipse.mylyn.internal.tasks.ui.util.TasksUiInternal; |
| import org.eclipse.mylyn.internal.tasks.ui.workingsets.TaskWorkingSetUpdater; |
| import org.eclipse.mylyn.tasks.core.ITask; |
| import org.eclipse.mylyn.tasks.core.TaskRepository; |
| import org.eclipse.mylyn.tasks.ui.TaskElementLabelProvider; |
| import org.eclipse.mylyn.tasks.ui.editors.TaskEditorInput; |
| import org.eclipse.swt.graphics.Image; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.ui.IEditorReference; |
| import org.eclipse.ui.IWorkbenchPage; |
| import org.eclipse.ui.IWorkbenchWindow; |
| import org.eclipse.ui.PartInitException; |
| import org.eclipse.ui.PlatformUI; |
| |
| /** |
| * @author Frank Becker |
| * @author Steffen Pingel |
| * @since 3.0 |
| */ |
| public class RepositoryCompletionProcessor implements IContentAssistProcessor { |
| |
| public static class TaskCompletionProposal implements ICompletionProposal { |
| |
| private final LabelProvider labelProvider; |
| |
| private final TaskRepository repository; |
| |
| private final ITask task; |
| |
| private final int replacementOffset; |
| |
| private final int replacementLength; |
| |
| private String replacement; |
| |
| private final String defaultReplacement; |
| |
| private final boolean includePrefix; |
| |
| public TaskCompletionProposal(TaskRepository repository, ITask task, LabelProvider labelProvider, |
| String defaultReplacement, boolean includePrefix, int replacementOffset, int replacementLength) { |
| this.labelProvider = labelProvider; |
| this.repository = repository; |
| this.task = task; |
| this.defaultReplacement = defaultReplacement; |
| this.includePrefix = includePrefix; |
| this.replacementOffset = replacementOffset; |
| this.replacementLength = replacementLength; |
| } |
| |
| public void apply(IDocument document) { |
| try { |
| document.replace(replacementOffset, replacementLength, getReplacement()); |
| } catch (BadLocationException x) { |
| // ignore |
| } |
| } |
| |
| public String getReplacement() { |
| if (replacement == null) { |
| // add an absolute reference to the task if the viewer does not have a repository |
| if (defaultReplacement == null || repository == null |
| || !repository.getRepositoryUrl().equals(task.getRepositoryUrl())) { |
| replacement = CopyTaskDetailsAction.getTextForTask(task); |
| } else if (includePrefix) { |
| replacement = TasksUiInternal.getTaskPrefix(task.getConnectorKind()) + defaultReplacement; |
| } else { |
| replacement = defaultReplacement; |
| } |
| } |
| return replacement; |
| } |
| |
| public String getAdditionalProposalInfo() { |
| return null; |
| } |
| |
| public IContextInformation getContextInformation() { |
| return null; |
| } |
| |
| public String getDisplayString() { |
| return labelProvider.getText(task); |
| } |
| |
| public Image getImage() { |
| return labelProvider.getImage(task); |
| } |
| |
| public Point getSelection(IDocument document) { |
| return new Point(replacementOffset + getReplacement().length(), 0); |
| } |
| |
| public TaskRepository getRepository() { |
| return repository; |
| } |
| |
| public ITask getTask() { |
| return task; |
| } |
| |
| } |
| |
| private class ProposalComputer { |
| |
| public static final String LABEL_SEPARATOR = " -------------------------------------------- "; //$NON-NLS-1$ |
| |
| private final Set<ITask> addedTasks = new HashSet<ITask>(); |
| |
| private boolean addSeparator; |
| |
| private final int offset; |
| |
| private final String prefix; |
| |
| private final List<ICompletionProposal> resultList = new ArrayList<ICompletionProposal>(); |
| |
| public ProposalComputer(ITextViewer viewer, int offset) { |
| this.offset = offset; |
| this.prefix = extractPrefix(viewer, offset).toLowerCase(); |
| } |
| |
| private void addProposal(ITask task, String replacement, boolean includeTaskPrefix) { |
| if (addSeparator) { |
| if (!addedTasks.isEmpty()) { |
| resultList.add(createSeparator()); |
| } |
| addSeparator = false; |
| } |
| |
| resultList.add(new TaskCompletionProposal(repository, task, labelProvider, replacement, includeTaskPrefix, |
| offset - prefix.length(), prefix.length())); |
| |
| addedTasks.add(task); |
| } |
| |
| public void addSeparator() { |
| addSeparator = true; |
| } |
| |
| public void addTasks(List<AbstractTask> tasks) { |
| for (AbstractTask task : tasks) { |
| addTask(task); |
| } |
| } |
| |
| public void addTask(ITask task) { |
| if (addedTasks.contains(task)) { |
| return; |
| } |
| |
| if (getNeverIncludePrefix() && !task.getRepositoryUrl().equals(repository.getRepositoryUrl())) { |
| return; |
| } |
| |
| String taskKey = task.getTaskKey(); |
| if (prefix.length() == 0) { |
| addProposal(task, taskKey, !getNeverIncludePrefix()); |
| } else if (taskKey != null && taskKey.startsWith(prefix)) { |
| // don't include prefix if completing id since it was most likely already added |
| addProposal(task, taskKey, false); |
| } else if (containsPrefix(task)) { |
| addProposal(task, taskKey, !getNeverIncludePrefix()); |
| } |
| } |
| |
| private boolean containsPrefix(ITask task) { |
| String haystack = TasksUiInternal.getTaskPrefix(task.getConnectorKind()) |
| + " " + labelProvider.getText(task); //$NON-NLS-1$ |
| String[] haystackTokens = haystack.split("\\s"); //$NON-NLS-1$ |
| String[] needles = prefix.trim().split("\\*"); //$NON-NLS-1$ |
| if (haystackTokens.length == 0 || needles.length == 0) { |
| return false; |
| } |
| // check if all needles are contained in haystack |
| for (String needle : needles) { |
| boolean matched = false; |
| haystack: for (String haystackToken : haystackTokens) { |
| if (haystackToken.toLowerCase().startsWith(needle)) { |
| matched = true; |
| break haystack; |
| } |
| } |
| if (!matched) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private CompletionProposal createSeparator() { |
| return new CompletionProposal("", offset, 0, 0, CommonImages.getImage(CommonImages.SEPARATOR_LIST), //$NON-NLS-1$ |
| LABEL_SEPARATOR, null, null); |
| } |
| |
| /** |
| * Returns the prefix of the currently completed text. Assumes that any character that is not a line break or |
| * white space can be part of a task id. |
| */ |
| private String extractPrefix(ITextViewer viewer, int offset) { |
| int i = offset; |
| IDocument document = viewer.getDocument(); |
| if (i > document.getLength()) { |
| return ""; //$NON-NLS-1$ |
| } |
| |
| try { |
| while (i > 0) { |
| char c = document.getChar(i - 1); |
| if (Character.isWhitespace(c) || c == '(' || c == ':') { |
| break; |
| } |
| i--; |
| } |
| if (i == offset && repository != null) { |
| // check if document contains "{prefix} " |
| String taskPrefix = TasksUiInternal.getTaskPrefix(repository.getConnectorKind()); |
| if (taskPrefix.length() > 1) { |
| try { |
| if (taskPrefix.equals(document.get(offset - taskPrefix.length(), taskPrefix.length()))) { |
| return taskPrefix; |
| } |
| } catch (BadLocationException e) { |
| // ignore |
| } |
| } |
| } |
| return document.get(i, offset - i); |
| } catch (BadLocationException e) { |
| return ""; //$NON-NLS-1$ |
| } |
| } |
| |
| public void filterTasks(List<AbstractTask> tasks) { |
| for (Iterator<AbstractTask> it = tasks.iterator(); it.hasNext();) { |
| ITask task = it.next(); |
| if (!select(task)) { |
| it.remove(); |
| } |
| } |
| } |
| |
| private boolean select(ITask task) { |
| return !(task instanceof LocalTask) // |
| && (repository == null || task.getRepositoryUrl().equals(repository.getRepositoryUrl())); |
| } |
| |
| public ICompletionProposal[] getResult() { |
| return resultList.toArray(new ICompletionProposal[resultList.size()]); |
| } |
| |
| } |
| |
| private static final int MAX_OPEN_EDITORS = 10; |
| |
| private static final int MAX_ACTIVATED_TASKS = 10; |
| |
| private final TaskElementLabelProvider labelProvider = new TaskElementLabelProvider(false); |
| |
| private final TaskRepository repository; |
| |
| private boolean neverIncludePrefix; |
| |
| public RepositoryCompletionProcessor(TaskRepository taskRepository) { |
| this.repository = taskRepository; |
| this.neverIncludePrefix = false; |
| } |
| |
| public boolean getNeverIncludePrefix() { |
| return neverIncludePrefix; |
| } |
| |
| public void setNeverIncludePrefix(boolean includePrefix) { |
| this.neverIncludePrefix = includePrefix; |
| } |
| |
| public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) { |
| ITextSelection selection = (ITextSelection) viewer.getSelectionProvider().getSelection(); |
| // adjust offset to end of normalized selection |
| if (selection.getOffset() == offset) { |
| offset = selection.getOffset() + selection.getLength(); |
| } |
| |
| ProposalComputer proposalComputer = new ProposalComputer(viewer, offset); |
| |
| // add tasks from navigation history |
| // IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); |
| // if (window != null) { |
| // IWorkbenchPage page = window.getActivePage(); |
| // if (page != null) { |
| // INavigationHistory history = page.getNavigationHistory(); |
| // INavigationLocation[] locations = history.getLocations(); |
| // if (locations != null) { |
| // for (INavigationLocation location : locations) { |
| // // location is always null |
| // } |
| // } |
| // } |
| // } |
| |
| // add open editor |
| IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); |
| if (window != null) { |
| IWorkbenchPage page = window.getActivePage(); |
| if (page != null) { |
| IEditorReference[] editorReferences = page.getEditorReferences(); |
| int count = 0; |
| for (int i = editorReferences.length - 1; i >= 0 && count < MAX_OPEN_EDITORS; i--) { |
| try { |
| if (editorReferences[i].getEditorInput() instanceof TaskEditorInput) { |
| TaskEditorInput input = (TaskEditorInput) editorReferences[i].getEditorInput(); |
| ITask task = input.getTask(); |
| if (task != null && !(task instanceof LocalTask)) { |
| proposalComputer.addTask(task); |
| count++; |
| } |
| } |
| } catch (PartInitException e) { |
| // ignore |
| } |
| } |
| } |
| } |
| |
| // add tasks from activation history |
| TaskActivationHistory taskHistory = TasksUiPlugin.getTaskActivityManager().getTaskActivationHistory(); |
| List<AbstractTask> tasks = taskHistory.getPreviousTasks(TasksUiInternal.getContainersFromWorkingSet(TaskWorkingSetUpdater.getActiveWorkingSets(window))); |
| int count = 0; |
| for (int i = tasks.size() - 1; i >= 0 && count < MAX_ACTIVATED_TASKS; i--) { |
| AbstractTask task = tasks.get(i); |
| if (!(task instanceof LocalTask)) { |
| proposalComputer.addTask(task); |
| } |
| } |
| |
| // add all remaining tasks for repository |
| if (repository != null) { |
| proposalComputer.addSeparator(); |
| |
| TaskList taskList = TasksUiPlugin.getTaskList(); |
| tasks = new ArrayList<AbstractTask>(taskList.getAllTasks()); |
| proposalComputer.filterTasks(tasks); |
| Collections.sort(tasks, new Comparator<AbstractTask>() { |
| public int compare(AbstractTask o1, AbstractTask o2) { |
| return labelProvider.getText(o1).compareTo(labelProvider.getText(o2)); |
| } |
| }); |
| proposalComputer.addTasks(tasks); |
| } |
| |
| return proposalComputer.getResult(); |
| } |
| |
| public IContextInformation[] computeContextInformation(ITextViewer viewer, int offset) { |
| return null; |
| } |
| |
| public char[] getCompletionProposalAutoActivationCharacters() { |
| return null; |
| } |
| |
| public char[] getContextInformationAutoActivationCharacters() { |
| return null; |
| } |
| |
| public IContextInformationValidator getContextInformationValidator() { |
| return null; |
| } |
| |
| public String getErrorMessage() { |
| return null; |
| } |
| |
| } |