/*******************************************************************************
* Copyright (c) 2008, 2013 Borland Software Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Borland Software Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.m2m.internal.qvt.oml.editor.ui.completion;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.jface.bindings.TriggerSequence;
import org.eclipse.jface.bindings.keys.KeySequence;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.contentassist.ContentAssistEvent;
import org.eclipse.jface.text.contentassist.ContentAssistant;
import org.eclipse.jface.text.contentassist.ICompletionListener;
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.text.source.ISourceViewer;
import org.eclipse.m2m.internal.qvt.oml.compiler.UnitProxy;
import org.eclipse.m2m.internal.qvt.oml.editor.ui.completion.collectorregistry.CategoryDescriptor;
import org.eclipse.m2m.internal.qvt.oml.editor.ui.completion.collectorregistry.CollectorDescriptor;
import org.eclipse.m2m.internal.qvt.oml.editor.ui.completion.collectorregistry.CollectorRegistry;
import org.eclipse.ocl.cst.CSTNode;
import org.eclipse.osgi.util.NLS;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.keys.IBindingService;
import org.eclipse.ui.texteditor.ITextEditor;
import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds;
/**
* @author aigdalov
*/
public class QvtCompletionProcessor implements IContentAssistProcessor {
private final static char[] ACTIVATION = {'.', '>' /* -> */, '@'};
private final int INITIAL_CATEGORY_INDEX = -1;
private final int NO_CATEGORY_INDEX = -1;
private final ContentAssistant myContentAssistant;
private List<CategoryDescriptor> myCategories = Collections.emptyList();
private int myCategoryIndex = INITIAL_CATEGORY_INDEX;
private final ITextEditor myEditor;
private int myOffset = -1;
public QvtCompletionProcessor(final ITextEditor editor, final ISourceViewer sourceViewer, ContentAssistant contentAssistant) {
myEditor = editor;
myContentAssistant = contentAssistant;
// See corresponding snippet in org.eclipse.jdt.internal.ui.text.java.ContentAssistProcessor
myContentAssistant.addCompletionListener(new ICompletionListener() {
/*
* @see org.eclipse.jface.text.contentassist.ICompletionListener#assistSessionStarted(org.eclipse.jface.text.contentassist.ContentAssistEvent)
*/
public void assistSessionStarted(ContentAssistEvent event) {
if (event.processor != QvtCompletionProcessor.this)
return;
myCategoryIndex = INITIAL_CATEGORY_INDEX;
if (event.assistant instanceof ContentAssistant) {
ContentAssistant contentAssistant = (ContentAssistant) event.assistant;
contentAssistant.setRepeatedInvocationMode(true);
contentAssistant.setStatusLineVisible(true);
KeySequence binding = getIterationBinding();
contentAssistant.setRepeatedInvocationTrigger(binding);
contentAssistant.setShowEmptyList(true);
}
}
/*
* @see org.eclipse.jface.text.contentassist.ICompletionListener#assistSessionEnded(org.eclipse.jface.text.contentassist.ContentAssistEvent)
*/
public void assistSessionEnded(ContentAssistEvent event) {
myOffset = -1;
}
/*
* @see org.eclipse.jface.text.contentassist.ICompletionListener#selectionChanged(org.eclipse.jface.text.contentassist.ICompletionProposal, boolean)
*/
public void selectionChanged(ICompletionProposal proposal, boolean smartToggle) {
}
private KeySequence getIterationBinding() {
final IBindingService bindingSvc= (IBindingService) PlatformUI.getWorkbench().getAdapter(IBindingService.class);
TriggerSequence binding= bindingSvc.getBestActiveBindingFor(ITextEditorActionDefinitionIds.CONTENT_ASSIST_PROPOSALS);
if (binding instanceof KeySequence)
return (KeySequence) binding;
return null;
}
});
}
public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) {
try {
UnitProxy unitProxy = (UnitProxy) myEditor.getAdapter(UnitProxy.class);
if(unitProxy == null) {
return null;
}
QvtCompletionData data = new QvtCompletionData(myEditor, viewer, unitProxy, offset);
if (!data.isValid()) {
return disableNextCodeCompletionPage();
}
if ((myCategoryIndex == INITIAL_CATEGORY_INDEX)
|| (myOffset == offset)) { // Ctrl + Space pressed again
int categoryIndex = updateCategoryIndex(data);
if (categoryIndex == NO_CATEGORY_INDEX) {
return disableNextCodeCompletionPage();
}
CategoryDescriptor nextCategory = getNextCategory();
if (nextCategory == null) {
disableNextCodeCompletionPage();
} else {
myContentAssistant.setStatusLineVisible(true);
myContentAssistant.setStatusMessage(NLS.bind(Messages.QvtCompletionProcessor_PressCtrlSpace,
nextCategory.getLabel()));
}
}
Collection<ICompletionProposal> proposals = new LinkedHashSet<ICompletionProposal>();
for (CollectorDescriptor collectorDescriptor : CollectorRegistry.getCollectors(getCurrentCategory().getId())) {
ICollector collector = collectorDescriptor.getCollector();
if (collector.isApplicable(data)) {
collector.addPropoposals(proposals, data);
}
}
return proposals.toArray(new ICompletionProposal[proposals.size()]);
} finally {
myOffset = offset;
}
}
private ICompletionProposal[] disableNextCodeCompletionPage() {
myContentAssistant.setStatusLineVisible(false);
return null;
}
private int updateCategoryIndex(QvtCompletionData data) {
if (myCategoryIndex == INITIAL_CATEGORY_INDEX) { /* -1 */
myCategories = new ArrayList<CategoryDescriptor>();
CategoryDescriptor[] allCategories = CollectorRegistry.getCategories();
for (CategoryDescriptor categoryDescriptor : allCategories) {
List<CollectorDescriptor> collectorDescriptors =
CollectorRegistry.getCollectors(categoryDescriptor.getId());
boolean isApplicable = false;
if (collectorDescriptors != null) {
for (CollectorDescriptor collectorDescriptor : collectorDescriptors) {
ICollector collector = collectorDescriptor.getCollector();
if (collector.isApplicable(data)) {
isApplicable = true;
break;
}
}
}
if (isApplicable) {
myCategories.add(categoryDescriptor);
}
}
}
if (myCategories.isEmpty()) {
return NO_CATEGORY_INDEX;
}
myCategoryIndex ++;
if (myCategoryIndex == myCategories.size()) {
myCategoryIndex = 0;
}
return myCategoryIndex;
}
public CategoryDescriptor getCurrentCategory() {
if (myCategories.isEmpty()) {
return null;
}
return myCategories.get(myCategoryIndex);
}
public CategoryDescriptor getNextCategory() {
if (myCategories.size() == 1) {
return null;
}
int index = myCategoryIndex + 1;
if (index == myCategories.size()) {
index = 0;
}
return myCategories.get(index);
}
public CategoryDescriptor getLastCategory() {
if (myCategories.isEmpty()) {
return null;
}
return myCategories.get(myCategories.size() - 1);
}
public static final CSTNode findLeftmostCSTNode(CSTNode root, int offset) {
CSTNode result = null;
for (TreeIterator<EObject> iterator = root.eAllContents(); iterator.hasNext(); ) {
EObject next = iterator.next();
if (next instanceof CSTNode) {
CSTNode child = (CSTNode) next;
if (child.getEndOffset() < offset) {
if ((result == null)
|| (child.getEndOffset() > result.getEndOffset())
|| ((child.getEndOffset() == result.getEndOffset())
&& (child.getStartOffset() < result.getStartOffset()))) {
result = child;
}
}
}
}
return result;
}
public IContextInformation[] computeContextInformation(ITextViewer viewer,
int offset) {
return null;
}
public char[] getCompletionProposalAutoActivationCharacters() {
return ACTIVATION;
}
public char[] getContextInformationAutoActivationCharacters() {
return null;
}
public IContextInformationValidator getContextInformationValidator() {
return null;
}
public String getErrorMessage() {
return null;
}
}