| author | mclay | 2008-11-09 09:41:58 (EST) |
|---|---|---|
| committer | sefftinge | 2008-11-09 09:41:58 (EST) |
| commit | 0dcacba2b3c50df06b552d01fdc6314c698c72c6 (patch) (side-by-side diff) | |
| tree | 5e4b1598978af0d96153730691c6c6d185bfdce0 | |
| parent | 6dc60517828857262bffb2d93186ff85844dfdd2 (diff) | |
| download | org.eclipse.xtext-0dcacba2b3c50df06b552d01fdc6314c698c72c6.zip org.eclipse.xtext-0dcacba2b3c50df06b552d01fdc6314c698c72c6.tar.gz org.eclipse.xtext-0dcacba2b3c50df06b552d01fdc6314c698c72c6.tar.bz2 | |
add: 'right-to-left' (backtrack?) capability and differentiate CA for crossref depending on if its linked or not linked
7 files changed, 532 insertions, 316 deletions
diff --git a/devtools/org.eclipse.xtext.reference.ui/src-gen/org/eclipse/xtext/reference/ReferenceGrammarGenProposalProvider.java b/devtools/org.eclipse.xtext.reference.ui/src-gen/org/eclipse/xtext/reference/ReferenceGrammarGenProposalProvider.java index 571d996..bc840df 100644 --- a/devtools/org.eclipse.xtext.reference.ui/src-gen/org/eclipse/xtext/reference/ReferenceGrammarGenProposalProvider.java +++ b/devtools/org.eclipse.xtext.reference.ui/src-gen/org/eclipse/xtext/reference/ReferenceGrammarGenProposalProvider.java @@ -52,7 +52,7 @@ public class ReferenceGrammarGenProposalProvider extends AbstractProposalProvid + assignment.getTerminal() + "' cardinality '" + assignment.getCardinality() + "' and prefix '" + prefix.trim() + "'"); } - return Collections.singletonList(createCompletionProposal("1", offset)); + return Collections.singletonList(createCompletionProposal(assignment,model,"1", offset)); } public List<? extends ICompletionProposal> completeSpielplatzBeschreibung(Assignment assignment, EObject model, String prefix, IDocument doc,int offset) { @@ -61,7 +61,7 @@ public class ReferenceGrammarGenProposalProvider extends AbstractProposalProvid + assignment.getTerminal() + "' cardinality '" + assignment.getCardinality() + "' and prefix '" + prefix.trim() + "'"); } - return Collections.singletonList(createCompletionProposal("\"SpielplatzBeschreibung\"", offset)); + return Collections.singletonList(createCompletionProposal(assignment,model,"\"SpielplatzBeschreibung\"", offset)); } public List<? extends ICompletionProposal> completeSpielplatzKinder(Assignment assignment, EObject model, String prefix, IDocument doc,int offset) { @@ -115,7 +115,7 @@ public class ReferenceGrammarGenProposalProvider extends AbstractProposalProvid + assignment.getTerminal() + "' cardinality '" + assignment.getCardinality() + "' and prefix '" + prefix.trim() + "'"); } - return Collections.singletonList(createCompletionProposal("KindName", offset)); + return Collections.singletonList(createCompletionProposal(assignment,model,"KindName", offset)); } public List<? extends ICompletionProposal> completeKindAge(Assignment assignment, EObject model, String prefix, IDocument doc,int offset) { @@ -124,7 +124,7 @@ public class ReferenceGrammarGenProposalProvider extends AbstractProposalProvid + assignment.getTerminal() + "' cardinality '" + assignment.getCardinality() + "' and prefix '" + prefix.trim() + "'"); } - return Collections.singletonList(createCompletionProposal("1", offset)); + return Collections.singletonList(createCompletionProposal(assignment,model,"1", offset)); } public List<? extends ICompletionProposal> completeErwachsenerName(Assignment assignment, EObject model, String prefix, IDocument doc,int offset) { @@ -133,7 +133,7 @@ public class ReferenceGrammarGenProposalProvider extends AbstractProposalProvid + assignment.getTerminal() + "' cardinality '" + assignment.getCardinality() + "' and prefix '" + prefix.trim() + "'"); } - return Collections.singletonList(createCompletionProposal("ErwachsenerName", offset)); + return Collections.singletonList(createCompletionProposal(assignment,model,"ErwachsenerName", offset)); } public List<? extends ICompletionProposal> completeErwachsenerAge(Assignment assignment, EObject model, String prefix, IDocument doc,int offset) { @@ -142,7 +142,7 @@ public class ReferenceGrammarGenProposalProvider extends AbstractProposalProvid + assignment.getTerminal() + "' cardinality '" + assignment.getCardinality() + "' and prefix '" + prefix.trim() + "'"); } - return Collections.singletonList(createCompletionProposal("1", offset)); + return Collections.singletonList(createCompletionProposal(assignment,model,"1", offset)); } public List<? extends ICompletionProposal> completeSpielzeugName(Assignment assignment, EObject model, String prefix, IDocument doc,int offset) { @@ -151,7 +151,7 @@ public class ReferenceGrammarGenProposalProvider extends AbstractProposalProvid + assignment.getTerminal() + "' cardinality '" + assignment.getCardinality() + "' and prefix '" + prefix.trim() + "'"); } - return Collections.singletonList(createCompletionProposal("SpielzeugName", offset)); + return Collections.singletonList(createCompletionProposal(assignment,model,"SpielzeugName", offset)); } public List<? extends ICompletionProposal> completeSpielzeugFarbe(Assignment assignment, EObject model, String prefix, IDocument doc,int offset) { @@ -215,7 +215,7 @@ public class ReferenceGrammarGenProposalProvider extends AbstractProposalProvid + assignment.getTerminal() + "' cardinality '" + assignment.getCardinality() + "' and prefix '" + prefix.trim() + "'"); } - return Collections.singletonList(createCompletionProposal("CustomTypeParserRuleName", offset)); + return Collections.singletonList(createCompletionProposal(assignment,model,"CustomTypeParserRuleName", offset)); } diff --git a/plugins/org.eclipse.xtext.ui.common/src/org/eclipse/xtext/ui/common/editor/codecompletion/AbstractProposalProvider.java b/plugins/org.eclipse.xtext.ui.common/src/org/eclipse/xtext/ui/common/editor/codecompletion/AbstractProposalProvider.java index 0eda0d4..ec42692 100644 --- a/plugins/org.eclipse.xtext.ui.common/src/org/eclipse/xtext/ui/common/editor/codecompletion/AbstractProposalProvider.java +++ b/plugins/org.eclipse.xtext.ui.common/src/org/eclipse/xtext/ui/common/editor/codecompletion/AbstractProposalProvider.java @@ -13,6 +13,7 @@ import org.eclipse.emf.ecore.EObject; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.contentassist.ICompletionProposal; import org.eclipse.jface.viewers.StyledString; +import org.eclipse.xtext.AbstractElement; import org.eclipse.xtext.AbstractRule; import org.eclipse.xtext.Assignment; import org.eclipse.xtext.CrossReference; @@ -58,9 +59,9 @@ public abstract class AbstractProposalProvider implements IProposalProvider { + prefix.trim() + "'"); } String text = keyword.getValue().length() == 1 ? keyword.getValue() : keyword.getValue() + " "; - return Collections.singletonList(createCompletionProposal(text, offset)); + return Collections.singletonList(createCompletionProposal(keyword, model, text, offset)); } - + /* * (non-Javadoc) * @see org.eclipse.xtext.ui.common.editor.codecompletion.IProposalProvider#completeRuleCall(org.eclipse.xtext.RuleCall, org.eclipse.emf.ecore.EObject, java.lang.String, org.eclipse.jface.text.IDocument, int) @@ -71,119 +72,78 @@ public abstract class AbstractProposalProvider implements IProposalProvider { logger.debug("completeRuleCall '" + ruleCall.getName() + "' cardinality '" + ruleCall.getCardinality() + "' for model '" + model + "' and prefix '" + prefix.trim() + "'"); } - + AbstractRule calledRule = GrammarUtil.calledRule(ruleCall); - + if (calledRule instanceof LexerRule) { - return doCompleteLexerRuleRuleCall((LexerRule) calledRule, ruleCall, offset); - } - + return doCompleteLexerRuleRuleCall((LexerRule) calledRule, ruleCall, model, offset); + } + return Collections.emptyList(); } - + /* * (non-Javadoc) - * @see org.eclipse.xtext.ui.common.editor.codecompletion.IProposalProvider#sortAndFilter(java.util.List, org.eclipse.emf.ecore.EObject, java.lang.String) + * @see org.eclipse.xtext.ui.common.editor.codecompletion.IProposalProvider#sortAndFilter(java.util.List, org.eclipse.emf.ecore.EObject, java.lang.String, org.eclipse.jface.text.IDocument, int) */ public List<? extends ICompletionProposal> sortAndFilter( - List<? extends ICompletionProposal> completionProposalList, EObject model, String prefix) { - return doSortAndFilter(completionProposalList,model,prefix); + List<? extends ICompletionProposal> completionProposalList, EObject model, String prefix, + IDocument document, int offset) { + return doSortAndFilter(completionProposalList, model, prefix, document, offset); } - - /** - * Concrete subclasses can override this for custom sort and filter - * behavior. Gets called after all completion proposals have been collected. - * - * The default behavior of this implementation is to sort duplicates and to trim - * matching <code>ICompletionProposal#displayString</code> with matching prefix values. - * - * @param completionProposalList matching {@link ICompletionProposal} to sort and filter - * @param model - the most specific model element under the cursor. - * @param prefix - the prefix under the cursor or null if there is no prefix - * @return the sorted and filtered <code>ICompletionProposal</code> list. - * - */ - protected List<? extends ICompletionProposal> doSortAndFilter( - List<? extends ICompletionProposal> completionProposalList,EObject model,String prefix) { - - if (model instanceof LeafNode) { - - LeafNode leafNode = (LeafNode) model; - - Map<String,ICompletionProposal> displayString2ICompletionProposalMap = new HashMap<String,ICompletionProposal>(); - - for (Iterator<? extends ICompletionProposal> iterator = completionProposalList.iterator(); iterator.hasNext();) { - ICompletionProposal completionProposal = iterator.next(); - - // filter duplicate displayString - if (!displayString2ICompletionProposalMap.containsKey(completionProposal.getDisplayString())) { - - displayString2ICompletionProposalMap.put(completionProposal.getDisplayString(), completionProposal); - - // trim displayString - if (leafNode.isHidden() && !"".equals(leafNode.getText().trim()) ) { - - if (completionProposal.getDisplayString().startsWith(leafNode.getText()) && - completionProposal instanceof XtextCompletionProposal) { - - XtextCompletionProposal xtextCompletionProposal = (XtextCompletionProposal) completionProposal; - - xtextCompletionProposal.setText(xtextCompletionProposal.getText().replaceFirst(leafNode.getText(), "")); - } else { - iterator.remove(); - } - } - } else { - iterator.remove(); - } - } - } - - return completionProposalList; - } - /** - * Concrete subclasses can override this to provide a more meaningful and sophisticated behaviour - * whenever a list of ICompletionProposal's should be computed for simple <code>LexerRule</code> call's. + * Concrete subclasses can override this to provide a more meaningful and + * sophisticated behaviour whenever a list of ICompletionProposal's should + * be computed for simple <code>LexerRule</code> call's. * - * This implementation returns one <code>ICompletionProposal</code> with a displayString composed - * of the name of the containing rule plus the featurename of an optional assignment and at the end the name - * of the given LexerRule (e.i. ParserRuleName+AssignmentFeatureName+LexerRuleName) or {@link #getDefaultIntegerValue()} - * if its <i>INT</i> based LexerRule. + * This implementation returns one <code>ICompletionProposal</code> with a + * displayString composed of the name of the containing rule plus the + * featurename of an optional assignment and at the end the name of the + * given LexerRule (e.i. ParserRuleName+AssignmentFeatureName+LexerRuleName) + * or {@link #getDefaultIntegerValue()} if its <i>INT</i> based LexerRule. * - * @param lexerRule the 'called' LexerRule instance - * @param ruleCall the ruleCall for the provided lexerRule - * @param offset an offset within the document for which completions should be computed - * @return a computed list of <code>ICompletionProposal</code> for the given <code>LexerRule</code> + * @param lexerRule + * the 'called' LexerRule instance + * @param ruleCall + * the ruleCall for the provided lexerRule + * @param offset + * an offset within the document for which completions should be + * computed + * @return a computed list of <code>ICompletionProposal</code> for the given + * <code>LexerRule</code> */ - protected List<? extends ICompletionProposal> doCompleteLexerRuleRuleCall(LexerRule lexerRule,RuleCall ruleCall, int offset) { + protected List<? extends ICompletionProposal> doCompleteLexerRuleRuleCall(LexerRule lexerRule, RuleCall ruleCall, + EObject model, int offset) { ParserRule containingParserRule = GrammarUtil.containingParserRule(ruleCall); Assignment containingAssignment = GrammarUtil.containingAssignment(ruleCall); - - String defaultDisplayString = containingParserRule.getName() + - (null != containingAssignment ? firstLetterCapitalized(containingAssignment.getFeature()) : "") + lexerRule.getName(); - + + String defaultDisplayString = containingParserRule.getName() + + (null != containingAssignment ? firstLetterCapitalized(containingAssignment.getFeature()) : "") + + lexerRule.getName(); + if (LEXER_RULE_INT.equalsIgnoreCase(lexerRule.getName())) { - defaultDisplayString=String.valueOf(getDefaultIntegerValue()); - } else if (LEXER_RULE_STRING.equalsIgnoreCase(lexerRule.getName())) { - defaultDisplayString="\""+defaultDisplayString+"\""; - } - - return Collections.singletonList(createCompletionProposal(defaultDisplayString, offset)); + defaultDisplayString = String.valueOf(getDefaultIntegerValue()); + } + else if (LEXER_RULE_STRING.equalsIgnoreCase(lexerRule.getName())) { + defaultDisplayString = "\"" + defaultDisplayString + "\""; + } + + return Collections.singletonList(createCompletionProposal(containingAssignment, model, defaultDisplayString, + offset)); } - + /** * @return the default integer value for ecore::EInt <code>RuleCall<> */ protected int getDefaultIntegerValue() { return 0; } - + /** * - * @return the id of the plug-in containing the image files; - * <code>null</code> is returned if the plug-in does not exist + * @return the id of the plug-in containing the image files; <code>null + * </code> is returned if the plug-in does not exist */ protected abstract String getPluginId(); @@ -209,14 +169,17 @@ public abstract class AbstractProposalProvider implements IProposalProvider { */ protected List<? extends ICompletionProposal> lookupCrossReference(CrossReference crossReference, EObject model, String prefix, int offset) { - + List<ICompletionProposal> completionProposalList = new ArrayList<ICompletionProposal>(); if (linkingService != null) { - EObject semanticModel = model instanceof AbstractNode?NodeUtil.getNearestSemanticObject((AbstractNode) model):model; - List<Pair<String, URI>> candidates = linkingService.getLinkCandidates(semanticModel, crossReference, prefix); + EObject semanticModel = model instanceof AbstractNode ? NodeUtil + .getNearestSemanticObject((AbstractNode) model) : model; + List<Pair<String, URI>> candidates = linkingService + .getLinkCandidates(semanticModel, crossReference, prefix); for (Pair<String, URI> candidate : candidates) { - completionProposalList.add(createCompletionProposal(candidate.getFirstElement(), offset)); + completionProposalList.add(createCompletionProposal(crossReference, model, candidate.getFirstElement(), + offset)); } } @@ -227,13 +190,103 @@ public abstract class AbstractProposalProvider implements IProposalProvider { * @return a new <code>XtextCompletionProposal</code> for the given text and * offset. */ - protected final XtextCompletionProposal createCompletionProposal(String text, int offset) { - return new XtextCompletionProposal(text, new StyledString(text), text, getDefaultImageFilePath(), - getPluginId(), offset); + protected final XtextCompletionProposal createCompletionProposal(AbstractElement abstractElement, EObject model, + String text, int offset) { + return new XtextCompletionProposal(abstractElement, model, text, new StyledString(text), text, + getDefaultImageFilePath(), getPluginId(), offset); + } + + /** + * Concrete subclasses can override this for custom sort and filter + * behavior. Gets called after all completion proposals have been collected. + * + * The default behavior of this implementation is to sort duplicates and to + * trim matching <code>ICompletionProposal#displayString</code> with + * matching prefix values. + * + * @param completionProposalList + * matching {@link ICompletionProposal} to sort and filter + * @param model + * - the most specific model element under the cursor. + * @param prefix + * - the prefix under the cursor or null if there is no prefix + * @param offset + * @param document + * @return the sorted and filtered <code>ICompletionProposal</code> list. + * + */ + protected List<? extends ICompletionProposal> doSortAndFilter( + List<? extends ICompletionProposal> completionProposalList, EObject model, String prefix, + IDocument document, int offset) { + + if (model instanceof LeafNode) { + + LeafNode leafNode = (LeafNode) model; + + Map<String, ICompletionProposal> displayString2ICompletionProposalMap = new HashMap<String, ICompletionProposal>(); + + for (Iterator<? extends ICompletionProposal> iterator = completionProposalList.iterator(); iterator + .hasNext();) { + + ICompletionProposal completionProposal = iterator.next(); + + // filter duplicate displayString + if (!displayString2ICompletionProposalMap.containsKey(completionProposal.getDisplayString())) { + + displayString2ICompletionProposalMap.put(completionProposal.getDisplayString(), completionProposal); + + boolean cursorIsAtTheEndOfTheLastElement = offset == (leafNode.getOffset() + leafNode.getLength()); + /** + * 1. filter and trim proposal's matching the prefix of the + * current leafNode (e.g. leafNode 'kin' for keyword 'kind' + * will be trimmed to 'd' , displayString stays the same) + * <p/> + * 2. special case for crossref: if the cursor is at the end + * of the previous element kind>|< we want to apply the + * filter only to instances of the previous grammarElement + * in order to always show all matches for 'right-to-left' + * backtracking use-cases + */ + if ((leafNode.isHidden() || cursorIsAtTheEndOfTheLastElement) + && !"".equals(leafNode.getText().trim()) + && completionProposal instanceof XtextCompletionProposal) { + + XtextCompletionProposal xtextCompletionProposal = (XtextCompletionProposal) completionProposal; + + AbstractElement abstractElement = xtextCompletionProposal.getAbstractElement(); + + EObject grammarElement = leafNode.getGrammarElement(); + // at the end of the last element we want to filter only + // the CompletionProposal for the same grammar element + if (completionProposal.getDisplayString().startsWith(leafNode.getText())) { + xtextCompletionProposal.setText(xtextCompletionProposal.getText().substring( + leafNode.getText().length())); + } + else if ((cursorIsAtTheEndOfTheLastElement && abstractElement.equals(grammarElement)) + || !cursorIsAtTheEndOfTheLastElement) { + if (logger.isDebugEnabled()) { + logger.debug("filter completionProposal '" + completionProposal + "'"); + } + iterator.remove(); + } + } + } + else { + if (logger.isDebugEnabled()) { + logger.debug("filter duplicate completionProposal '" + completionProposal + "'"); + } + + iterator.remove(); + } + } + } + + return completionProposalList; } /** - * @param text to apply + * @param text + * to apply * @return the provided string with the first letter capitalized */ protected final String firstLetterCapitalized(String text) { 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 7b3c99e..3a31dc3 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,13 +1,8 @@ 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.Collection; -import java.util.HashMap; -import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.List; -import java.util.Map; import java.util.Set; import org.apache.log4j.Logger; @@ -25,15 +20,9 @@ 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.Assignment; import org.eclipse.xtext.CrossReference; import org.eclipse.xtext.EcoreUtil2; import org.eclipse.xtext.GrammarUtil; -import org.eclipse.xtext.Keyword; -import org.eclipse.xtext.ParserRule; -import org.eclipse.xtext.RuleCall; -import org.eclipse.xtext.TypeRef; import org.eclipse.xtext.parser.IParseResult; import org.eclipse.xtext.parsetree.AbstractNode; import org.eclipse.xtext.parsetree.CompositeNode; @@ -58,7 +47,6 @@ public class DefaultContentAssistProcessor implements IContentAssistProcessor { @Inject private IProposalProvider proposalProvider; - private final Map<String, Method> methodLookupMap = new HashMap<String, Method>(); /** * computes the possible grammar elements following the one at the given @@ -93,25 +81,37 @@ public class DefaultContentAssistProcessor implements IContentAssistProcessor { LeafNode currentLeafNode = ParseTreeUtil.getCurrentNodeByOffset(rootNode, offset); String prefix = null==currentLeafNode ? "" : currentLeafNode.getText(); - - Set<AbstractElement> nextValidElementSet = ParseTreeUtil.getElementSetValidFromOffset(rootNode,lastCompleteNode, offset); + + Set<AbstractElement> nextValidElementSet = new LinkedHashSet<AbstractElement>(); + /** + * in the case of a non linked crossreference we delegate to proposalProvider (again) + */ + if (lastCompleteNode.getGrammarElement() instanceof CrossReference && !isLinked(lastCompleteNode)) { + + nextValidElementSet.add((AbstractElement) lastCompleteNode.getGrammarElement()); + } /** - * in the case of a non linked crossreference we delegate to proposalProvider (again) + * in the case we are at the end of the completed previous element add for 'right-to-left' backtracking cases */ - if (lastCompleteNode.getGrammarElement() instanceof CrossReference && !isLinked(lastCompleteNode) ) { - nextValidElementSet.add((AbstractElement) lastCompleteNode.getGrammarElement().eContainer()); + else if (currentLeafNode==lastCompleteNode) { + nextValidElementSet.add((AbstractElement) lastCompleteNode.getGrammarElement()); } + nextValidElementSet.addAll(ParseTreeUtil.getElementSetValidFromOffset(rootNode,lastCompleteNode, offset)); + + ProposalProviderInvokerSwitch proposalProviderInvokerSwitch = new ProposalProviderInvokerSwitch( + lastCompleteNode, document, offset, prefix, proposalProvider); + for (List<EObject> resolvedElementOrRuleList : new ProposalCandidateResolverSwitch(nextValidElementSet)) { - List<ICompletionProposal> collectedCompletionProposalList = collectCompletionProposalList(resolvedElementOrRuleList, xtextDocument, - lastCompleteNode, prefix, offset); + List<ICompletionProposal> collectedCompletionProposalList = + proposalProviderInvokerSwitch.collectCompletionProposalList(resolvedElementOrRuleList); completionProposalList.addAll(collectedCompletionProposalList); } if (completionProposalList != null) { - List<? extends ICompletionProposal> sortAndFilter = proposalProvider.sortAndFilter(completionProposalList,currentLeafNode,prefix); + List<? extends ICompletionProposal> sortAndFilter = proposalProvider.sortAndFilter(completionProposalList,currentLeafNode,prefix,document,offset); completionProposals = sortAndFilter.toArray(new ICompletionProposal[] {}); } } @@ -140,210 +140,42 @@ public class DefaultContentAssistProcessor implements IContentAssistProcessor { return new ContextInformationValidator(this); } - private List<ICompletionProposal> collectCompletionProposalList(List<EObject> resolvedElementOrRuleList, IDocument document, AbstractNode currentLeafNode, - String prefix, final int offset) { - - List<ICompletionProposal> completionProposalList = new ArrayList<ICompletionProposal>(); - - for (Iterator<EObject> elementOrRuleIterator = resolvedElementOrRuleList.iterator(); elementOrRuleIterator - .hasNext();) { - EObject abstractElement = elementOrRuleIterator.next(); - - if (abstractElement instanceof Keyword) { - completionProposalList.addAll(proposalProvider.completeKeyword((Keyword) abstractElement, - currentLeafNode, prefix, document, offset)); - } - else if (abstractElement instanceof Assignment) { - - Assignment assignment = (Assignment) abstractElement; - - ParserRule parserRule = GrammarUtil.containingParserRule(assignment); - - EObject model = null == ((CompositeNode) currentLeafNode.eContainer()).getElement() ? currentLeafNode - .eContainer() : ((CompositeNode) currentLeafNode.eContainer()).getElement(); - - Method method = findMethod(proposalProvider.getClass(), "complete" - + firstLetterCapitalized(parserRule.getName()) - + firstLetterCapitalized(assignment.getFeature()), Assignment.class, model.getClass(), - String.class, document.getClass(), int.class); - - Collection<? extends ICompletionProposal> assignmentProposalList = null == method ? null - : invokeMethod(method, proposalProvider, assignment, model, prefix, document, offset); - - if (null != assignmentProposalList) { - completionProposalList.addAll(assignmentProposalList); - } - - } - else if (abstractElement instanceof RuleCall) { - - EObject model = null == ((CompositeNode) currentLeafNode.eContainer()).getElement() ? currentLeafNode - .eContainer() : ((CompositeNode) currentLeafNode.eContainer()).getElement(); - - List<? extends ICompletionProposal> ruleCallProposalList = this.proposalProvider.completeRuleCall( - (RuleCall) abstractElement, model, prefix, document, offset); - - if (null != ruleCallProposalList) { - completionProposalList.addAll(ruleCallProposalList); - } - - AbstractRule calledRule = GrammarUtil.calledRule((RuleCall) abstractElement); - - if (calledRule.getType() != null) { - - TypeRef typeRef = calledRule.getType(); - - Method method = findMethod(proposalProvider.getClass(), "complete" - + firstLetterCapitalized(typeRef.getAlias()) + firstLetterCapitalized(typeRef.getName()), - RuleCall.class, model.getClass(), String.class, document.getClass(), int.class); - - Collection<? extends ICompletionProposal> proposalList = null == method ? null : invokeMethod( - method, proposalProvider, abstractElement, model, prefix, document, offset); - - if (null != proposalList) { - completionProposalList.addAll(proposalList); - } - - } - } - } - - return completionProposalList; - } - - 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"); - Method result = methodLookupMap.get(name); - Class<?> searchType = clazz; - while (!Object.class.equals(searchType) && searchType != null && null == result) { - 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 || equalOrAssignableTypes(method.getParameterTypes(), paramTypes))) { - if (result == null - || equalOrAssignableTypes(result.getParameterTypes(), method.getParameterTypes())) { - result = method; - methodLookupMap.put(name, method); - } - } - } - searchType = searchType.getSuperclass(); - } - return result; - } @SuppressWarnings("unchecked") - private final Collection<ICompletionProposal> invokeMethod(Method method, Object target, Object... args) { - - try { - return (Collection<ICompletionProposal>) method.invoke(target, args); - } - catch (Exception ex) { - handleReflectionException(ex); - } - throw new IllegalStateException("huh?"); - } - - private 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 void handleUnexpectedException(Throwable ex) { - IllegalStateException isex = new IllegalStateException("Unexpected exception thrown"); - isex.initCause(ex); - throw isex; - } - - private final void rethrowRuntimeException(Throwable ex) { - if (ex instanceof RuntimeException) { - throw (RuntimeException) ex; - } - if (ex instanceof Error) { - throw (Error) ex; - } - handleUnexpectedException(ex); - } - - private boolean equalOrAssignableTypes(Class<?>[] a, Class<?>[] a2) { - if (a == a2) { - return true; - } - - if (a == null || a2 == null) { - return false; - } + private boolean isLinked(AbstractNode lastCompleteNode) { - int length = a.length; + EObject semanticModel = NodeUtil.getNearestSemanticObject(lastCompleteNode); - if (a2.length != length) { - return false; - } + EReference eReference = getReference((CrossReference) lastCompleteNode.getGrammarElement(), semanticModel + .eClass()); - for (int i = 0; i < length; i++) { - Class<?> o1 = a[i]; - Class<?> o2 = a2[i]; + if (eReference.getUpperBound() == 1) { - if (!(o1 == null ? o2 == null : o1.equals(o2) || o1.isAssignableFrom(o2))) { - return false; - } - } - return true; - } - - private final String firstLetterCapitalized(String name) { - return name.substring(0, 1).toUpperCase() + name.substring(1); - } - - @SuppressWarnings("unchecked") - private boolean isLinked(AbstractNode lastCompleteNode) { - - EObject semanticModel = NodeUtil.getNearestSemanticObject(lastCompleteNode); - - EReference eReference = getReference((CrossReference) lastCompleteNode.getGrammarElement(), semanticModel.eClass()); - - if (eReference.getUpperBound() == 1 ) { - - if (null!=semanticModel.eGet(eReference)) { + if (null != semanticModel.eGet(eReference)) { return true; } } - else { - + else { + EcoreEList<EObject> ecoreEList = (EcoreEList<EObject>) semanticModel.eGet(eReference); - - for (Iterator<EObject> iterator = ecoreEList.iterator(); iterator.hasNext();) { - - EObject object = iterator.next(); - - if (EcoreUtil2.getURIFragment(object).equalsIgnoreCase(((LeafNode)lastCompleteNode).getText())) { + + for (EObject object : ecoreEList) { + + if (EcoreUtil2.getURIFragment(object).equalsIgnoreCase(((LeafNode) lastCompleteNode).getText())) { return true; } } } - + return false; } - + private EReference getReference(CrossReference ref, EClass class1) { - + EList<EReference> references = class1.getEAllReferences(); - + String feature = GrammarUtil.containingAssignment(ref).getFeature(); - + for (EReference reference : references) { if (!reference.isContainment() && reference.getName().equals(feature)) return reference; 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 394988f..88db9a5 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 @@ -71,8 +71,8 @@ public interface IProposalProvider { * This method is invoked by the framework after all possible completions have been collected. * @param completionProposalList matching {@link ICompletionProposal} to sort and filter * @param model - the most specific model element under the cursor. - * @param prefix - the prefix under the cursor or null if there is no prefix * @return the sorted and filtered <code>ICompletionProposal</code> list. + * */ - public List<? extends ICompletionProposal> sortAndFilter(List<? extends ICompletionProposal> completionProposalList,EObject model,String prefix); + public List<? extends ICompletionProposal> sortAndFilter(List<? extends ICompletionProposal> completionProposalList,EObject model, String prefix, IDocument document,int offset); } diff --git a/plugins/org.eclipse.xtext.ui.common/src/org/eclipse/xtext/ui/common/editor/codecompletion/ProposalCandidateResolverSwitch.java b/plugins/org.eclipse.xtext.ui.common/src/org/eclipse/xtext/ui/common/editor/codecompletion/ProposalCandidateResolverSwitch.java index 4028f49..b3e31ea 100644 --- a/plugins/org.eclipse.xtext.ui.common/src/org/eclipse/xtext/ui/common/editor/codecompletion/ProposalCandidateResolverSwitch.java +++ b/plugins/org.eclipse.xtext.ui.common/src/org/eclipse/xtext/ui/common/editor/codecompletion/ProposalCandidateResolverSwitch.java @@ -19,6 +19,7 @@ import org.eclipse.xtext.AbstractElement; import org.eclipse.xtext.AbstractRule; 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.ParserRule; @@ -26,7 +27,7 @@ import org.eclipse.xtext.RuleCall; import org.eclipse.xtext.util.XtextSwitch; /** - * Represents <b>Switch</b> for the model's inheritance hierarchy to resolve + * Represents a <b>Switch</b> for the Xtext model's inheritance hierarchy to resolve * potential completion proposal candidate's. * * It supports the call {@link #doSwitch(EObject) doSwitch(object)} instead of @@ -115,6 +116,11 @@ public class ProposalCandidateResolverSwitch extends XtextSwitch<List<EObject>> } return elementList; } + + @Override + public List<EObject> caseCrossReference(CrossReference crossReference) { + return Collections.singletonList(crossReference.eContainer()); + } @Override public List<EObject> defaultCase(EObject object) { diff --git a/plugins/org.eclipse.xtext.ui.common/src/org/eclipse/xtext/ui/common/editor/codecompletion/ProposalProviderInvokerSwitch.java b/plugins/org.eclipse.xtext.ui.common/src/org/eclipse/xtext/ui/common/editor/codecompletion/ProposalProviderInvokerSwitch.java new file mode 100644 index 0000000..b12e918 --- a/dev/null +++ b/plugins/org.eclipse.xtext.ui.common/src/org/eclipse/xtext/ui/common/editor/codecompletion/ProposalProviderInvokerSwitch.java @@ -0,0 +1,271 @@ +/******************************************************************************* + * Copyright (c) 2008 itemis AG (http://www.itemis.eu) 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 + * + *******************************************************************************/ +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.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.contentassist.ICompletionProposal; +import org.eclipse.xtext.AbstractRule; +import org.eclipse.xtext.Assignment; +import org.eclipse.xtext.GrammarUtil; +import org.eclipse.xtext.Keyword; +import org.eclipse.xtext.ParserRule; +import org.eclipse.xtext.RuleCall; +import org.eclipse.xtext.TypeRef; +import org.eclipse.xtext.parsetree.AbstractNode; +import org.eclipse.xtext.parsetree.CompositeNode; +import org.eclipse.xtext.util.XtextSwitch; + +/** + * Represents a <b>Switch</b> for the Xtext model's inheritance hierarchy to + * callback and invoke the completion methods of <code>IProposalProvider</code> + * implementations to gather and collect <code>ICompletionProposal</code> to be + * displayed for Content Assist. + * + * It supports the call {@link #doSwitch(EObject) doSwitch(object)} instead of + * typical <code>if (x instanceof y)</code> code blocks. + * + * @author Michael Clay - Initial contribution and API + * @see IProposalProvider + * @see org.eclipse.xtext.util.XtextSwitch + */ +public class ProposalProviderInvokerSwitch extends XtextSwitch<List<ICompletionProposal>> { + + private List<ICompletionProposal> completionProposalList = new ArrayList<ICompletionProposal>(); + + private final IProposalProvider proposalProvider; + + private final IDocument document; + + private final AbstractNode currentLeafNode; + + private final String prefix; + + private int offset; + + private static final Map<String, Method> methodLookupMap = new HashMap<String, Method>(); + + /** + * + * @param currentLeafNode + * the last node in the document + * @param document + * the document itself + * @param offset + * the position in the offset from which CA was triggered + * @param prefix + * the prefix normally consisting of the text content of the + * currentLeafNode + * @param proposalProvider + * the proposalProvider to callback and invoke + */ + public ProposalProviderInvokerSwitch(AbstractNode currentLeafNode, IDocument document, int offset, String prefix, + IProposalProvider proposalProvider) { + super(); + this.currentLeafNode = currentLeafNode; + this.document = document; + this.offset = offset; + this.prefix = prefix; + this.proposalProvider = proposalProvider; + } + + /** + * Collect a list of <code>ICompletionProposal</code> for the given + * elementList through callback and reflective invocations to + * <code>IProposalProvider</code> + * + * @param elementList + * the list of <code>EObject</code> to collect + * <code>ICompletionProposal</code> for + * @return a list of matching <code>ICompletionProposal</code> + */ + public List<ICompletionProposal> collectCompletionProposalList(List<EObject> elementList) { + + completionProposalList = new ArrayList<ICompletionProposal>(); + + for (Iterator<EObject> elementOrRuleIterator = elementList.iterator(); elementOrRuleIterator.hasNext();) { + EObject abstractElement = elementOrRuleIterator.next(); + doSwitch(abstractElement); + } + + return completionProposalList; + + } + + @Override + public List<ICompletionProposal> caseAssignment(Assignment assignment) { + ParserRule parserRule = GrammarUtil.containingParserRule(assignment); + + EObject model = null == ((CompositeNode) currentLeafNode.eContainer()).getElement() ? currentLeafNode + .eContainer() : ((CompositeNode) currentLeafNode.eContainer()).getElement(); + + Method method = findMethod(proposalProvider.getClass(), "complete" + + firstLetterCapitalized(parserRule.getName()) + firstLetterCapitalized(assignment.getFeature()), + Assignment.class, model.getClass(), String.class, document.getClass(), int.class); + + Collection<? extends ICompletionProposal> assignmentProposalList = null == method ? null : invokeMethod(method, + proposalProvider, assignment, model, prefix, document, offset); + + if (null != assignmentProposalList) { + completionProposalList.addAll(assignmentProposalList); + } + + return null; + } + + @Override + public List<ICompletionProposal> caseKeyword(Keyword keyword) { + completionProposalList.addAll(proposalProvider.completeKeyword(keyword, currentLeafNode, prefix, document, + offset)); + return null; + } + + @Override + public List<ICompletionProposal> caseRuleCall(RuleCall ruleCall) { + + EObject model = null == ((CompositeNode) currentLeafNode.eContainer()).getElement() ? currentLeafNode + .eContainer() : ((CompositeNode) currentLeafNode.eContainer()).getElement(); + + + List<? extends ICompletionProposal> ruleCallProposalList = this.proposalProvider.completeRuleCall(ruleCall, + model, prefix, document, offset); + + if (null != ruleCallProposalList) { + completionProposalList.addAll(ruleCallProposalList); + } + + AbstractRule calledRule = GrammarUtil.calledRule(ruleCall); + + if (calledRule.getType() != null) { + + TypeRef typeRef = calledRule.getType(); + + Method method = findMethod(proposalProvider.getClass(), "complete" + + firstLetterCapitalized(typeRef.getAlias()) + firstLetterCapitalized(typeRef.getName()), + RuleCall.class, model.getClass(), String.class, document.getClass(), int.class); + + Collection<? extends ICompletionProposal> proposalList = null == method ? null : invokeMethod(method, + proposalProvider, ruleCall, model, prefix, document, offset); + + if (null != proposalList) { + completionProposalList.addAll(proposalList); + } + + } + return null; + } + + 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"); + Method result = methodLookupMap.get(name); + Class<?> searchType = clazz; + while (!Object.class.equals(searchType) && searchType != null && null == result) { + 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 || equalOrAssignableTypes(method.getParameterTypes(), paramTypes))) { + if (result == null + || equalOrAssignableTypes(result.getParameterTypes(), method.getParameterTypes())) { + result = method; + methodLookupMap.put(name, method); + } + } + } + searchType = searchType.getSuperclass(); + } + return result; + } + + @SuppressWarnings("unchecked") + private final Collection<ICompletionProposal> invokeMethod(Method method, Object target, Object... args) { + + try { + return (Collection<ICompletionProposal>) method.invoke(target, args); + } + catch (Exception ex) { + handleReflectionException(ex); + } + throw new IllegalStateException("huh?"); + } + + private 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 void handleUnexpectedException(Throwable ex) { + IllegalStateException isex = new IllegalStateException("Unexpected exception thrown"); + isex.initCause(ex); + throw isex; + } + + private final void rethrowRuntimeException(Throwable ex) { + if (ex instanceof RuntimeException) { + throw (RuntimeException) ex; + } + if (ex instanceof Error) { + throw (Error) ex; + } + handleUnexpectedException(ex); + } + + private boolean equalOrAssignableTypes(Class<?>[] a, Class<?>[] a2) { + if (a == a2) { + return true; + } + + if (a == null || a2 == null) { + return false; + } + + int length = a.length; + + if (a2.length != length) { + return false; + } + + for (int i = 0; i < length; i++) { + Class<?> o1 = a[i]; + Class<?> o2 = a2[i]; + + if (!(o1 == null ? o2 == null : o1.equals(o2) || o1.isAssignableFrom(o2))) { + return false; + } + } + return true; + } + + private final String firstLetterCapitalized(String name) { + return name.substring(0, 1).toUpperCase() + name.substring(1); + } + +} diff --git a/plugins/org.eclipse.xtext.ui.common/src/org/eclipse/xtext/ui/common/editor/codecompletion/XtextCompletionProposal.java b/plugins/org.eclipse.xtext.ui.common/src/org/eclipse/xtext/ui/common/editor/codecompletion/XtextCompletionProposal.java index 9428dbd..ffeef6d 100644 --- a/plugins/org.eclipse.xtext.ui.common/src/org/eclipse/xtext/ui/common/editor/codecompletion/XtextCompletionProposal.java +++ b/plugins/org.eclipse.xtext.ui.common/src/org/eclipse/xtext/ui/common/editor/codecompletion/XtextCompletionProposal.java @@ -2,6 +2,7 @@ package org.eclipse.xtext.ui.common.editor.codecompletion; import org.apache.log4j.Logger; import org.eclipse.core.runtime.Assert; +import org.eclipse.emf.ecore.EObject; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.DocumentEvent; @@ -15,6 +16,7 @@ import org.eclipse.jface.text.contentassist.IContextInformation; import org.eclipse.jface.viewers.StyledString; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; +import org.eclipse.xtext.AbstractElement; /** * Default Xtext implementation of interface <code>ICompletionProposal</code>. @@ -28,8 +30,11 @@ import org.eclipse.swt.graphics.Point; public class XtextCompletionProposal implements ICompletionProposal, ICompletionProposalExtension2, ICompletionProposalExtension6 { - private Logger logger = Logger.getLogger(XtextCompletionProposal.class); + protected final Logger logger = Logger.getLogger(XtextCompletionProposal.class); + + private EObject model; + private AbstractElement abstractElement; private String text; private String description; private Image image; @@ -38,6 +43,9 @@ public class XtextCompletionProposal implements ICompletionProposal, private String pluginIdentifier; /** + * @param element + * the element for which this CompletionProposal is created for + * @param model the last semtantically complete object * @param text * the text value to be replaced/inserted * @param label @@ -52,12 +60,14 @@ public class XtextCompletionProposal implements ICompletionProposal, * @param offset * the offset of the text */ - public XtextCompletionProposal(String text, StyledString label, + public XtextCompletionProposal(AbstractElement element,EObject model,String text, StyledString label, String description, String imageFilePath, String pluginIdentifier, int offset) { Assert.isNotNull(text, "parameter 'text' must not be null"); Assert.isNotNull(pluginIdentifier, "pluginIdentifier 'text' must not be null"); + this.abstractElement = element; + this.model = model; this.text = text; this.description = description; this.offset = offset; @@ -74,6 +84,45 @@ public class XtextCompletionProposal implements ICompletionProposal, } /** + * + * Getter for model property. + * + * @return the associated model object + */ + public EObject getModel() { + return model; + } + + /** + * Setter for model property. + * + * @param model to set + */ + public void setModel(EObject model) { + this.model = model; + } + + + /** + * Getter for abstractElement property. + * + * @return the associated element + */ + public AbstractElement getAbstractElement() { + return abstractElement; + } + + + /** + * Setter for abstractElement property. + * @param abstractElement to set + */ + public void setAbstractElement(AbstractElement abstractElement) { + this.abstractElement = abstractElement; + } + + + /** * Setter for the text to insert * * @param text @@ -227,6 +276,11 @@ public class XtextCompletionProposal implements ICompletionProposal, public Point getSelection(IDocument document) { return new Point(offset + this.text.length(), 0); } + + @Override + public String toString() { + return "XtextCompletionPoposal[text='"+getText()+"']"; + } private void initializeImage(String imageName) { Image newImage = JFaceResources.getImage(imageName); |

