summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormclay2008-10-27 04:28:57 (EDT)
committer sefftinge2008-10-27 04:28:57 (EDT)
commit29cfd89db7e0b960c3cac979bfca985dd94e5601 (patch)
tree46926e925d82b75bb275793c7e5b8b27a7b8930a
parent1309b99faeba6b5f9a3f2a65e7a2c1497b23f5b4 (diff)
downloadorg.eclipse.xtext-29cfd89db7e0b960c3cac979bfca985dd94e5601.zip
org.eclipse.xtext-29cfd89db7e0b960c3cac979bfca985dd94e5601.tar.gz
org.eclipse.xtext-29cfd89db7e0b960c3cac979bfca985dd94e5601.tar.bz2
add: start working on bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=250383 (Implementation of org.eclipse.xtext.ui.common.editor.codecompletion.DefaultContentAssistProcessor)
-rw-r--r--devtools/org.eclipse.xtext.reference/src/org/eclipse/xtext/reference/ReferenceGrammar.xtext5
-rw-r--r--plugins/org.eclipse.xtext.ui.common/src/org/eclipse/xtext/ui/common/editor/codecompletion/DefaultContentAssistProcessor.java318
-rw-r--r--plugins/org.eclipse.xtext.ui.common/src/org/eclipse/xtext/ui/common/editor/codecompletion/IProposalProvider.java8
3 files changed, 310 insertions, 21 deletions
diff --git a/devtools/org.eclipse.xtext.reference/src/org/eclipse/xtext/reference/ReferenceGrammar.xtext b/devtools/org.eclipse.xtext.reference/src/org/eclipse/xtext/reference/ReferenceGrammar.xtext
index e339612..edb1d9e 100644
--- a/devtools/org.eclipse.xtext.reference/src/org/eclipse/xtext/reference/ReferenceGrammar.xtext
+++ b/devtools/org.eclipse.xtext.reference/src/org/eclipse/xtext/reference/ReferenceGrammar.xtext
@@ -10,7 +10,7 @@ language org. eclipse. xtext. reference. ReferenceGrammar
generate ReferenceGrammar "http://eclipse.org/xtext/reference/ReferenceGrammar"
Spielplatz :
- ( "spielplatz" groesse = INT ( beschreibung = STRING ) ? "{" ( kinder += Kind | erzieher += Erwachsener | spielzeuge += Spielzeug ) * "}" ) ? ;
+ ( "spielplatz" groesse = INT ( beschreibung = STRING ) ? "{" ( kinder += Kind | erzieher += Erwachsener | spielzeuge += Spielzeug | familie += Familie ) * "}" ) ? ;
Person :
Kind | Erwachsener ;
@@ -24,5 +24,8 @@ generate ReferenceGrammar "http://eclipse.org/xtext/reference/ReferenceGrammar"
Spielzeug :
"spielzeug" "(" name = ID farbe = Farbe ")" ;
+ Familie :
+ "familie" "(" mutter=[Erwachsener] vater=[Erwachsener] kinder+=[Kind] ("," kinder+=[Kind])* ")" ;
+
Farbe :
wert=("ROT" | "BLAU" | "GELB" | "GRÜN"); \ No newline at end of file
diff --git a/plugins/org.eclipse.xtext.ui.common/src/org/eclipse/xtext/ui/common/editor/codecompletion/DefaultContentAssistProcessor.java b/plugins/org.eclipse.xtext.ui.common/src/org/eclipse/xtext/ui/common/editor/codecompletion/DefaultContentAssistProcessor.java
index 5fadb46..5e5dc74 100644
--- a/plugins/org.eclipse.xtext.ui.common/src/org/eclipse/xtext/ui/common/editor/codecompletion/DefaultContentAssistProcessor.java
+++ b/plugins/org.eclipse.xtext.ui.common/src/org/eclipse/xtext/ui/common/editor/codecompletion/DefaultContentAssistProcessor.java
@@ -1,8 +1,17 @@
package org.eclipse.xtext.ui.common.editor.codecompletion;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Set;
import org.eclipse.core.runtime.Assert;
+import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextViewer;
@@ -11,7 +20,17 @@ 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.xtext.AbstractElement;
+import org.eclipse.xtext.AbstractRule;
+import org.eclipse.xtext.Action;
+import org.eclipse.xtext.Alternatives;
+import org.eclipse.xtext.Assignment;
+import org.eclipse.xtext.CrossReference;
+import org.eclipse.xtext.GrammarUtil;
+import org.eclipse.xtext.Group;
import org.eclipse.xtext.Keyword;
+import org.eclipse.xtext.LexerRule;
+import org.eclipse.xtext.ParserRule;
import org.eclipse.xtext.RuleCall;
import org.eclipse.xtext.parser.IParseResult;
import org.eclipse.xtext.parsetree.AbstractNode;
@@ -50,29 +69,232 @@ public class DefaultContentAssistProcessor implements IContentAssistProcessor {
}
});
Assert.isNotNull(rootNode);
- LeafNode currentNode = ParseTreeUtil.getCurrentNodeByOffset(rootNode, offset);
- // AbstractNode lastNode =
- // ParseTreeUtil.getLastCompleteNodeByOffset(rootNode, offset);
- String prefix = viewer.getTextWidget().getText(currentNode.getOffset(), offset);
- List<ICompletionProposal> allProposals = null;
- EObject grammarElement = currentNode.getGrammarElement();
- if (grammarElement instanceof Keyword) {
- allProposals = proposalProvider.completeKeyword((Keyword) grammarElement,
- rootNode, prefix, xtextDocument);
- }
- else if (grammarElement instanceof RuleCall) {
- // FIXME allProposals =
- // proposalProvider.completeRuleCall((RuleCall) currentNode,
- // rootNode, prefix);
+
+ // last COMPLETE node element (node with associated grammar element)
+ AbstractNode lastCompleteNode = ParseTreeUtil.getLastCompleteNodeByOffset(rootNode, offset);
+ // node at the CURRENT cursor position (node with or without grammar element)
+ LeafNode currentLeafNode = (LeafNode) ParseTreeUtil.getCurrentNodeByOffset(rootNode, offset);
+ // get associated grammar element
+ AbstractElement grammarElement = ParseTreeUtil.getGrammarElementFromNode(lastCompleteNode);
+
+ String prefix = viewer.getTextWidget().getText(currentLeafNode.getOffset(), offset);
+
+ List<ICompletionProposal> completionProposalList = new ArrayList<ICompletionProposal>();
+
+ for (Iterator<AbstractElement> iterator = calculatePossibleElementSet(lastCompleteNode, grammarElement)
+ .iterator(); iterator.hasNext();) {
+ AbstractElement nextElement = iterator.next();
+
+ List<EObject> resolvedElementOrRuleList = resolveElement(nextElement);
+
+ for (Iterator<EObject> elementOrRuleIterator = resolvedElementOrRuleList.iterator(); elementOrRuleIterator
+ .hasNext();) {
+ EObject abstractElement = elementOrRuleIterator.next();
+
+ if (abstractElement instanceof Keyword) {
+ completionProposalList.addAll(proposalProvider.completeKeyword((Keyword) abstractElement,
+ currentLeafNode, prefix, xtextDocument, offset));
+ }
+ else if (abstractElement instanceof Assignment) {
+
+ Assignment assignment = (Assignment) abstractElement;
+
+ ParserRule lexerRule = GrammarUtil.containingParserRule(assignment);
+
+ Method method = findMethod(proposalProvider.getClass(), "complete"
+ + firstLetterCapitalized(lexerRule.getName())
+ + firstLetterCapitalized(assignment.getFeature()), Assignment.class, EObject.class,
+ String.class, IDocument.class, int.class);
+
+ Collection<? extends ICompletionProposal> assignmentProposalList = (Collection<? extends ICompletionProposal>) invokeMethod(method, proposalProvider, assignment, currentLeafNode, prefix, xtextDocument,
+ offset);
+
+ completionProposalList.addAll(assignmentProposalList);
+ }
+ else if (abstractElement instanceof LexerRule) {
+ }
+ else if (abstractElement instanceof RuleCall) {
+ }
+ else if (abstractElement instanceof CrossReference) {
+ }
+ else if (abstractElement instanceof Action) {
+ }
+ }
}
- if (allProposals != null) {
- return (ICompletionProposal[]) proposalProvider.sortAndFilter(allProposals).toArray();
+
+ if (completionProposalList != null) {
+ List<ICompletionProposal> sortAndFilter = proposalProvider.sortAndFilter(completionProposalList);
+ return (ICompletionProposal[]) sortAndFilter.toArray(new ICompletionProposal[] {});
+
}
}
}
return null;
}
+ protected final Set<AbstractElement> calculatePossibleElementSet(AbstractNode contextNode,
+ AbstractElement grammarElement) {
+
+ Assert.isNotNull(contextNode, "parameter 'contextNode' must not be null");
+ Assert.isNotNull(grammarElement, "parameter 'grammarElement' must not be null");
+
+ Set<AbstractElement> elementSet = new LinkedHashSet<AbstractElement>();
+
+ if (grammarElement.eContainer() instanceof ParserRule) {
+
+ /**
+ * we have completed the rule of the current context.continue at the
+ * parent context
+ */
+ boolean hasLeafNodes = false;
+
+ for (Iterator<LeafNode> iterator = contextNode.getLeafNodes().listIterator(); !hasLeafNodes
+ && iterator.hasNext(); hasLeafNodes = !iterator.next().isHidden()) {
+ ;
+ }
+
+ contextNode = contextNode.getParent();
+
+ while (contextNode != null && contextNode.getGrammarElement() == null) {
+ contextNode = contextNode.getParent();
+ }
+
+ if (null != contextNode) {
+
+ elementSet.addAll(calculatePossibleElementSet(contextNode, ParseTreeUtil
+ .getGrammarElementFromNode(contextNode)));
+
+ }
+ else if (grammarElement.eContainer() instanceof ParserRule) {
+
+ if (!hasLeafNodes || GrammarUtil.isMultipleCardinality(grammarElement)) {
+ elementSet.add(grammarElement);
+ }
+
+ }
+
+ }
+ else if (grammarElement.eContainer() instanceof Alternatives) {
+ /**
+ * one out of the alternatives is already fullfilled so we can
+ * simply skip and proceed to the parent
+ */
+
+ elementSet.addAll(calculatePossibleElementSet(contextNode, (AbstractElement) grammarElement.eContainer()));
+
+ }
+ else if (grammarElement.eContainer() instanceof Group) {
+
+ EList<AbstractElement> contents = ((Group) grammarElement.eContainer()).getAbstractTokens();
+
+ int indexOf = contents.indexOf(grammarElement) + 1;
+
+ int size = contents.size();
+
+ // add the current one if has an oneOrMore cardinality
+ if (GrammarUtil.isOneOrMoreCardinality(grammarElement)) {
+ elementSet.add(grammarElement);
+ }
+
+ /**
+ * start at the current (maybe the last) or at the following one
+ * with optional cardinality and add all following with optional
+ * cardinality
+ */
+ AbstractElement last = GrammarUtil.isAnyCardinality(grammarElement) || indexOf == size ? grammarElement
+ : contents.get(indexOf++);
+
+ while (GrammarUtil.isOptionalCardinality(last) && indexOf < size) {
+ elementSet.add(last);
+ last = indexOf < size ? contents.get(indexOf++) : last;
+ }
+
+ // always add the following if available or the last one if has an
+ // any cardinality
+ if (last != grammarElement || GrammarUtil.isAnyCardinality(last)) {
+ elementSet.add(last);
+ }
+
+ // ask parent groups only if we've completed the whole group
+ if (indexOf == size) {
+
+ boolean startedAtlastGrammarElementInGroup = last == grammarElement;
+
+ if (startedAtlastGrammarElementInGroup || GrammarUtil.isOptionalCardinality(last)) {
+
+ elementSet.addAll(calculatePossibleElementSet(contextNode, (AbstractElement) grammarElement
+ .eContainer()));
+ }
+
+ }
+
+ }
+ else {
+ elementSet.addAll(calculatePossibleElementSet(contextNode, (AbstractElement) grammarElement.eContainer()));
+
+ }
+
+ return elementSet;
+ }
+
+ protected final List<EObject> resolveElement(AbstractElement abstractElement) {
+
+ List<EObject> elementList = new ArrayList<EObject>();
+
+ if (abstractElement instanceof Alternatives) {
+ for (AbstractElement alternativeElement : ((Alternatives) abstractElement).getGroups()) {
+ elementList.addAll(resolveElement(alternativeElement));
+ }
+ }
+ else if (abstractElement instanceof Assignment) {
+
+ Assignment assignment = (Assignment) abstractElement;
+
+ if (assignment.getTerminal() instanceof RuleCall) {
+
+ AbstractRule abstractRule = GrammarUtil.calledRule((RuleCall) assignment.getTerminal());
+
+ if (abstractRule instanceof ParserRule) {
+ elementList.addAll(resolveElement(assignment.getTerminal()));
+ }
+ else {
+ elementList.add(assignment);
+ }
+ }
+ else {
+ elementList.add(assignment);
+ }
+ }
+ else if (abstractElement instanceof RuleCall) {
+ AbstractRule abstractRule = GrammarUtil.calledRule((RuleCall) abstractElement);
+
+ if (null == abstractRule) {
+ elementList.add(abstractElement);
+ }
+ else if (abstractRule instanceof LexerRule) {
+ elementList.add(abstractRule);
+ }
+ else {
+ return resolveElement(((ParserRule) abstractRule).getAlternatives());
+ }
+ }
+ else if (abstractElement instanceof Group) {
+ boolean includeNext = true;
+ for (Iterator<AbstractElement> iterator = ((Group) abstractElement).getAbstractTokens().iterator(); iterator
+ .hasNext()
+ && includeNext;) {
+ AbstractElement groupElement = iterator.next();
+ elementList.addAll(resolveElement(groupElement));
+ includeNext = GrammarUtil.isOptionalCardinality(groupElement);
+ }
+
+ }
+ else {
+ elementList.add(abstractElement);
+ }
+ return elementList;
+ }
+
public char[] getCompletionProposalAutoActivationCharacters() {
return null;
}
@@ -93,4 +315,68 @@ public class DefaultContentAssistProcessor implements IContentAssistProcessor {
return new ContextInformationValidator(this);
}
+ protected void handleReflectionException(Exception ex) {
+ if (ex instanceof NoSuchMethodException) {
+ throw new IllegalStateException("Method not found: " + ex.getMessage());
+ }
+ if (ex instanceof IllegalAccessException) {
+ throw new IllegalStateException("Could not access method: " + ex.getMessage());
+ }
+ if (ex instanceof InvocationTargetException) {
+ rethrowRuntimeException(((InvocationTargetException) ex).getTargetException());
+ }
+ if (ex instanceof RuntimeException) {
+ throw (RuntimeException) ex;
+ }
+ handleUnexpectedException(ex);
+ }
+
+ private final void rethrowRuntimeException(Throwable ex) {
+ if (ex instanceof RuntimeException) {
+ throw (RuntimeException) ex;
+ }
+ if (ex instanceof Error) {
+ throw (Error) ex;
+ }
+ handleUnexpectedException(ex);
+ }
+
+ protected void handleUnexpectedException(Throwable ex) {
+ IllegalStateException isex = new IllegalStateException("Unexpected exception thrown");
+ isex.initCause(ex);
+ throw isex;
+ }
+
+ private final String firstLetterCapitalized(String name) {
+ return name.substring(0, 1).toUpperCase() + name.substring(1);
+ }
+
+ private final Method findMethod(Class<?> clazz, String name, Class<?>... paramTypes) {
+ Assert.isNotNull(clazz, "Class must not be null");
+ Assert.isNotNull(name, "Method name must not be null");
+ Class<?> searchType = clazz;
+ while (!Object.class.equals(searchType) && searchType != null) {
+ Method[] methods = (searchType.isInterface() ? searchType.getMethods() : searchType.getDeclaredMethods());
+ for (int i = 0; i < methods.length; i++) {
+ Method method = methods[i];
+ if (name.equals(method.getName())
+ && (paramTypes == null || Arrays.equals(paramTypes, method.getParameterTypes()))) {
+ return method;
+ }
+ }
+ searchType = searchType.getSuperclass();
+ }
+ return null;
+ }
+
+ private final Object invokeMethod(Method method, Object target, Object... args) {
+ try {
+ return method.invoke(target, args);
+ }
+ catch (Exception ex) {
+ handleReflectionException(ex);
+ }
+ throw new IllegalStateException("huh?");
+ }
+
}
diff --git a/plugins/org.eclipse.xtext.ui.common/src/org/eclipse/xtext/ui/common/editor/codecompletion/IProposalProvider.java b/plugins/org.eclipse.xtext.ui.common/src/org/eclipse/xtext/ui/common/editor/codecompletion/IProposalProvider.java
index a3cf92e..1e80bf5 100644
--- a/plugins/org.eclipse.xtext.ui.common/src/org/eclipse/xtext/ui/common/editor/codecompletion/IProposalProvider.java
+++ b/plugins/org.eclipse.xtext.ui.common/src/org/eclipse/xtext/ui/common/editor/codecompletion/IProposalProvider.java
@@ -16,7 +16,7 @@ import org.eclipse.xtext.RuleCall;
* In addition to the declared methods, the framework tries to call grammar dependent methods for assignments using reflection.
* The signature of such methods invoked reflectively follows the following pattern:
*
- * public List<ICompletionProposal> complete[Typename]_[featureName](Assignment ele, EObject model, String prefix)
+ * public List<ICompletionProposal> complete[Typename][featureName](Assignment ele, EObject model, String prefix)
*
* <b>Example</b>
* Given the following grammar :
@@ -27,12 +27,12 @@ import org.eclipse.xtext.RuleCall;
*
* One could provide the following method in an implementation of this interface:
* <code>
- * public List<ICompletionProposal> completeMyType_name(Assignment ele, EObject model, String prefix, IDocument doc) {...}
+ * public List<ICompletionProposal> completeMyTypeName(Assignment ele, EObject model, String prefix, IDocument doc) {...}
* </code>
* Note that if you have generated Java classes for your domain model (meta model) you can alternatively declare the second parameter using
* a specific type:
* <code>
- * public List<ICompletionProposal> completeMyType_name(Assignment ele, MyType model, String prefix, IDocument doc) {...}
+ * public List<ICompletionProposal> completeMyTypeName(Assignment ele, MyType model, String prefix, IDocument doc) {...}
* </code>
*
*/
@@ -48,7 +48,7 @@ public interface IProposalProvider {
* @param doc - the IDocument instance used by the editor
* @return
*/
- public List<ICompletionProposal> completeKeyword(Keyword ele, EObject model, String prefix, IDocument doc);
+ public List<ICompletionProposal> completeKeyword(Keyword ele, EObject model, String prefix, IDocument doc,int offset);
/**
* Is invoked by the framework if (with respect to the grammar) it is possible that the keyword passed as first parameter