/*******************************************************************************
* Copyright (c) 2010-2012, Zoltan Ujhelyi, Istvan Rath and Daniel Varro
* 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:
* Zoltan Ujhelyi - initial API and implementation
*******************************************************************************/
package org.eclipse.incquery.patternlanguage.emf.ui.contentassist;
import static org.eclipse.emf.ecore.util.EcoreUtil.getRootContainer;
import java.util.Set;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EDataType;
import org.eclipse.emf.ecore.EEnum;
import org.eclipse.emf.ecore.EEnumLiteral;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.incquery.patternlanguage.emf.EMFPatternLanguageScopeHelper;
import org.eclipse.incquery.patternlanguage.emf.ResolutionException;
import org.eclipse.incquery.patternlanguage.emf.eMFPatternLanguage.ClassType;
import org.eclipse.incquery.patternlanguage.emf.eMFPatternLanguage.EMFPatternLanguagePackage;
import org.eclipse.incquery.patternlanguage.emf.eMFPatternLanguage.PackageImport;
import org.eclipse.incquery.patternlanguage.emf.eMFPatternLanguage.PatternModel;
import org.eclipse.incquery.patternlanguage.emf.helper.EMFPatternLanguageHelper;
import org.eclipse.incquery.patternlanguage.patternLanguage.PathExpressionElement;
import org.eclipse.incquery.patternlanguage.patternLanguage.PathExpressionHead;
import org.eclipse.incquery.patternlanguage.patternLanguage.Pattern;
import org.eclipse.incquery.patternlanguage.patternLanguage.PatternBody;
import org.eclipse.incquery.patternlanguage.patternLanguage.Variable;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.xtext.Assignment;
import org.eclipse.xtext.EnumRule;
import org.eclipse.xtext.Keyword;
import org.eclipse.xtext.RuleCall;
import org.eclipse.xtext.naming.QualifiedName;
import org.eclipse.xtext.nodemodel.ICompositeNode;
import org.eclipse.xtext.nodemodel.ILeafNode;
import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
import org.eclipse.xtext.resource.EObjectDescription;
import org.eclipse.xtext.resource.IEObjectDescription;
import org.eclipse.xtext.scoping.IScope;
import org.eclipse.xtext.scoping.IScopeProvider;
import org.eclipse.xtext.ui.editor.contentassist.ConfigurableCompletionProposal;
import org.eclipse.xtext.ui.editor.contentassist.ContentAssistContext;
import org.eclipse.xtext.ui.editor.contentassist.ICompletionProposalAcceptor;
import com.google.common.base.Function;
import com.google.common.base.Predicates;
import com.google.common.collect.Sets;
import com.google.inject.Inject;
/**
* see http://www.eclipse.org/Xtext/documentation/latest/xtext.html#contentAssist on how to customize content assistant
*/
public class EMFPatternLanguageProposalProvider extends AbstractEMFPatternLanguageProposalProvider {
private static final Set<String> FILTERED_KEYWORDS = Sets.newHashSet("pattern");
@Inject
IScopeProvider scopeProvider;
@Inject
ReferenceProposalCreator crossReferenceProposalCreator;
/*
* (non-Javadoc)
*
* @see org.eclipse.xtext.xbase.ui.contentassist.XbaseProposalProvider#completeKeyword(org.eclipse.xtext.Keyword,
* org.eclipse.xtext.ui.editor.contentassist.ContentAssistContext,
* org.eclipse.xtext.ui.editor.contentassist.ICompletionProposalAcceptor)
*/
@SuppressWarnings("restriction")
@Override
public void completeKeyword(Keyword keyword, ContentAssistContext contentAssistContext,
ICompletionProposalAcceptor acceptor) {
// ignore keywords in FILTERED set
if (FILTERED_KEYWORDS.contains(keyword.getValue())) {
return;
}
super.completeKeyword(keyword, contentAssistContext, acceptor);
}
@Override
public void complete_ValueReference(EObject model, RuleCall ruleCall, ContentAssistContext context,
ICompletionProposalAcceptor acceptor) {
super.complete_ValueReference(model, ruleCall, context, acceptor);
if (model instanceof PathExpressionHead) {
PathExpressionHead head = (PathExpressionHead) model;
try {
// XXX The following code re-specifies scoping instead of reusing the scope provider
EClassifier typeClassifier = EMFPatternLanguageScopeHelper.calculateExpressionType(head);
if (typeClassifier instanceof EEnum) {
// In case of EEnums add Enum Literal constants
EEnum type = (EEnum) typeClassifier;
ContentAssistContext.Builder myContextBuilder = context.copy();
myContextBuilder.setMatcher(new EnumPrefixMatcher(type.getName()));
for (EEnumLiteral literal : type.getELiterals()) {
ICompletionProposal completionProposal =
createCompletionProposal("::" + literal.getName(),
type.getName() + "::" + literal.getName(),
null,
myContextBuilder.toContext());
acceptor.accept(completionProposal);
}
}
// XXX The following code re-specifies scoping instead of reusing the scope provider
// Always refer to existing variables
PatternBody body = (PatternBody) head.eContainer()/* PathExpression */.eContainer()/* PatternBody */;
for (Variable var : body.getVariables()) {
acceptor.accept(createCompletionProposal(var.getName(), context));
}
Pattern pattern = (Pattern) body.eContainer();
for (Variable var : pattern.getParameters()) {
acceptor.accept(createCompletionProposal(var.getName(), context));
}
} catch (ResolutionException e) {
// If resolution fails, simply don't return anything
}
}
}
@Override
public void completeType_Typename(EObject model, Assignment assignment, ContentAssistContext context,
ICompletionProposalAcceptor acceptor) {
PatternModel pModel = null;
EObject root = getRootContainer(model);
if (root instanceof PatternModel) {
pModel = (PatternModel) root;
}
ContentAssistContext.Builder myContextBuilder = context.copy();
myContextBuilder.setMatcher(new ClassifierPrefixMatcher(context.getMatcher(), getQualifiedNameConverter()));
ClassType type = null;
if (model instanceof Variable) {
type = (ClassType) ((Variable) model).getType();
} else {
return;
}
ICompositeNode node = NodeModelUtils.getNode(type);
int offset = node.getOffset();
Region replaceRegion = new Region(offset, context.getReplaceRegion().getLength()
+ context.getReplaceRegion().getOffset() - offset);
myContextBuilder.setReplaceRegion(replaceRegion);
myContextBuilder.setLastCompleteNode(node);
StringBuilder availablePrefix = new StringBuilder(4);
for (ILeafNode leaf : node.getLeafNodes()) {
if (leaf.getGrammarElement() != null && !leaf.isHidden()) {
if ((leaf.getTotalLength() + leaf.getTotalOffset()) < context.getOffset())
availablePrefix.append(leaf.getText());
else
availablePrefix.append(leaf.getText().substring(0, context.getOffset() - leaf.getTotalOffset()));
}
if (leaf.getTotalOffset() >= context.getOffset())
break;
}
myContextBuilder.setPrefix(availablePrefix.toString());
ContentAssistContext myContext = myContextBuilder.toContext();
for (PackageImport declaration : EMFPatternLanguageHelper.getPackageImportsIterable(pModel)) {
if (declaration.getEPackage() != null) {
createClassifierProposals(declaration, model, myContext, acceptor);
}
}
}
@SuppressWarnings("restriction")
private void createClassifierProposals(PackageImport declaration, EObject model, ContentAssistContext context,
ICompletionProposalAcceptor acceptor) {
// String alias = declaration.getAlias();
// QualifiedName prefix = (!Strings.isEmpty(alias))
// ? QualifiedName.create(getValueConverter().toString(alias,"ID"))
// : null;
boolean createDatatypeProposals = modelOrContainerIs(model, Variable.class);
boolean createEnumProposals = modelOrContainerIs(model, EnumRule.class);
boolean createClassProposals = modelOrContainerIs(model, Variable.class);
Function<IEObjectDescription, ICompletionProposal> factory = getProposalFactory(null, context);
for (EClassifier classifier : declaration.getEPackage().getEClassifiers()) {
if (classifier instanceof EDataType && createDatatypeProposals || classifier instanceof EEnum
&& createEnumProposals || classifier instanceof EClass && createClassProposals) {
String classifierName = getValueConverter().toString(classifier.getName(), "ID");
QualifiedName proposalQualifiedName = /* (prefix != null) ? prefix.append(classifierName) : */QualifiedName
.create(classifierName);
IEObjectDescription description = EObjectDescription.create(proposalQualifiedName, classifier);
ConfigurableCompletionProposal proposal = (ConfigurableCompletionProposal) factory.apply(description);
if (proposal != null) {
/*
* if (prefix != null) proposal.setDisplayString(classifier.getName() + " - " + alias);
*/
proposal.setPriority(proposal.getPriority() * 2);
}
acceptor.accept(proposal);
}
}
}
private boolean modelOrContainerIs(EObject model, Class<?>... types) {
for (Class<?> type : types) {
if (type.isInstance(model) || type.isInstance(model.eContainer()))
return true;
}
return false;
}
@SuppressWarnings("restriction")
public void complete_RefType(PathExpressionElement model, RuleCall ruleCall, ContentAssistContext context,
ICompletionProposalAcceptor acceptor) {
IScope scope = scopeProvider.getScope(model.getTail(),
EMFPatternLanguagePackage.Literals.REFERENCE_TYPE__REFNAME);
crossReferenceProposalCreator.lookupCrossReference(scope, model,
EMFPatternLanguagePackage.Literals.REFERENCE_TYPE__REFNAME, acceptor,
Predicates.<IEObjectDescription> alwaysTrue(),
getProposalFactory(ruleCall.getRule().getName(), context));
}
@Override
public void completeRefType_Refname(EObject model, Assignment assignment, ContentAssistContext context,
ICompletionProposalAcceptor acceptor) {
// This method is deliberately empty.
// This override prohibits the content assist to suggest incorrect parameters.
}
}