| author | mclay | 2008-12-08 11:41:25 (EST) |
|---|---|---|
| committer | sefftinge | 2008-12-08 11:41:25 (EST) |
| commit | 683549db652665646a12f40a177cda9c21c15897 (patch) (side-by-side diff) | |
| tree | e4f95b9bb02cf20d420ed68a4c5d1af7d02bb97c | |
| parent | 4f0a72c5fc39729f59b87c5ccbc631c471a93758 (diff) | |
| download | org.eclipse.xtext-683549db652665646a12f40a177cda9c21c15897.zip org.eclipse.xtext-683549db652665646a12f40a177cda9c21c15897.tar.gz org.eclipse.xtext-683549db652665646a12f40a177cda9c21c15897.tar.bz2 | |
NEW - bug 257940: [Content Assist] new bug for expression like grammar reported from TMF mailinglist
https://bugs.eclipse.org/bugs/show_bug.cgi?id=257940
6 files changed, 422 insertions, 224 deletions
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 f360507..3d84e4a 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 @@ -2,6 +2,7 @@ package org.eclipse.xtext.ui.common.editor.codecompletion; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -26,10 +27,7 @@ import org.eclipse.xtext.RuleCall; import org.eclipse.xtext.crossref.IScopedElement; import org.eclipse.xtext.crossref.impl.SimpleAttributeResolver; import org.eclipse.xtext.parsetree.AbstractNode; -import org.eclipse.xtext.parsetree.CompositeNode; import org.eclipse.xtext.parsetree.LeafNode; -import org.eclipse.xtext.parsetree.NodeUtil; -import org.eclipse.xtext.parsetree.ParseTreeUtil; import org.eclipse.xtext.resource.XtextResource; import org.eclipse.xtext.service.Inject; import org.eclipse.xtext.util.Strings; @@ -47,6 +45,8 @@ public abstract class AbstractProposalProvider implements IProposalProvider { protected static final String LEXER_RULE_ID = "ID"; protected static final String LEXER_RULE_INT = "INT"; protected static final String LEXER_RULE_STRING = "STRING"; + + protected static final Comparator<ICompletionProposal> PROPOSAL_COMPARATOR= new ProposalComparator(); // logger available to subclasses protected final Logger logger = Logger.getLogger(getClass()); @@ -66,9 +66,7 @@ public abstract class AbstractProposalProvider implements IProposalProvider { /* * (non-Javadoc) * - * @see - * org.eclipse.xtext.ui.common.editor.codecompletion.IProposalProvider#completeKeyword(org.eclipse.xtext.Keyword, - * org.eclipse.emf.ecore.EObject, java.lang.String, org.eclipse.jface.text.IDocument, int) + * @see org.eclipse.xtext.ui.common.editor.codecompletion.IProposalProvider#completeKeyword(org.eclipse.xtext.Keyword,org.eclipse.emf.ecore.EObject, java.lang.String, org.eclipse.jface.text.IDocument, int) */ public List<? extends ICompletionProposal> completeKeyword(Keyword keyword, EObject model, String prefix, IDocument doc, int offset) { @@ -83,9 +81,7 @@ public abstract class AbstractProposalProvider implements IProposalProvider { /* * (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) + * @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) */ public List<? extends ICompletionProposal> completeRuleCall(RuleCall ruleCall, EObject model, String prefix, IDocument doc, int offset) { @@ -106,9 +102,7 @@ public abstract class AbstractProposalProvider implements IProposalProvider { /* * (non-Javadoc) * - * @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, - * org.eclipse.xtext.parsetree.AbstractNode, org.eclipse.xtext.parsetree.LeafNode) + * @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, org.eclipse.xtext.parsetree.AbstractNode, org.eclipse.xtext.parsetree.LeafNode) */ public List<? extends ICompletionProposal> sortAndFilter( List<? extends ICompletionProposal> completionProposalList, EObject model, String prefix, @@ -125,12 +119,9 @@ public abstract class AbstractProposalProvider implements IProposalProvider { * (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 + * @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, @@ -190,15 +181,23 @@ public abstract class AbstractProposalProvider implements IProposalProvider { if (linkingCandidatesService != null) { - final XtextResource xtextResource = (XtextResource) model.eResource(); - final ParserRule containingParserRule = GrammarUtil.containingParserRule(crossReference); - final EClass eClass = xtextResource.getElementFactory().getEClass(GrammarUtil.getReturnTypeName(containingParserRule)); - final EReference ref = GrammarUtil.getReference(crossReference, eClass); - final Iterable<IScopedElement> candidates = linkingCandidatesService.getLinkingCandidates(model, ref); - final String trimmedPrefix = prefix.trim(); + XtextResource xtextResource = (XtextResource) model.eResource(); + + ParserRule containingParserRule = GrammarUtil.containingParserRule(crossReference); + + EClass eClass = xtextResource.getElementFactory(). + getEClass(GrammarUtil.getReturnTypeName(containingParserRule)); + + EReference ref = GrammarUtil.getReference(crossReference, eClass); + + Iterable<IScopedElement> candidates = linkingCandidatesService.getLinkingCandidates(model, ref); + + String trimmedPrefix = prefix.trim(); + for (IScopedElement candidate : candidates) { if (isCandidateMatchingPrefix(model, ref, candidate, trimmedPrefix)) { - completionProposalList.add(createCompletionProposal(crossReference, model, candidate.name(), offset)); + completionProposalList.add( + createCompletionProposal(crossReference, model, candidate.name(), offset)); } } } @@ -224,7 +223,7 @@ public abstract class AbstractProposalProvider implements IProposalProvider { } /** - * Concrete subclasses can override this for custom sort and filter behavior. Gets called after all completion + * Concrete subclasses can override this for custom sort and filter behavior. Called right after all completion * proposals have been collected. * * The default behavior of this implementation is to sort duplicates and to trim matching @@ -242,82 +241,71 @@ public abstract class AbstractProposalProvider implements IProposalProvider { ICompletionProposal completionProposal = iterator.next(); - // filter duplicate displayString + // filter duplicate if (!displayString2ICompletionProposalMap.containsKey(completionProposal.getDisplayString())) { displayString2ICompletionProposalMap.put(completionProposal.getDisplayString(), completionProposal); - if (model != null) { - - // filter by prefix - // TODO: this works only if we have access to the corresponding grammarelement - if (completionProposal instanceof XtextCompletionProposal) { - - XtextCompletionProposal xtextCompletionProposal = (XtextCompletionProposal) completionProposal; - - AbstractElement abstractElement = null; - - if (xtextCompletionProposal.getAbstractElement() instanceof Keyword || - xtextCompletionProposal.getAbstractElement() instanceof CrossReference) { - abstractElement = GrammarUtil.containingAssignment(xtextCompletionProposal.getAbstractElement()); - } - - if (null==abstractElement) { - abstractElement = xtextCompletionProposal.getAbstractElement(); - } - - CompositeNode rootNode = NodeUtil.getRootNode(model); - - AbstractNode lastCompleteNode = ParseTreeUtil.getLastCompleteNodeByOffset(rootNode, offset); - - LeafNode currentLeafNode = ParseTreeUtil.getCurrentNodeByOffset(rootNode, offset); - - EObject grammarElement = GrammarUtil.containingAssignment(currentLeafNode.getGrammarElement()); - - if (null==grammarElement) { - grammarElement = currentLeafNode.getGrammarElement(); - } - - boolean atTheEndOfTheLastCompleteNode = currentLeafNode == lastCompleteNode; - - boolean candidateToCompare = false; - - // means if we are at the end of a complete token we want to filter only equal grammarelements (not the 'next' ones) - if (atTheEndOfTheLastCompleteNode && abstractElement.equals(grammarElement)) { - candidateToCompare = true; - } else if (!atTheEndOfTheLastCompleteNode ) { - candidateToCompare = true; - } - - if ( candidateToCompare && (!"".equals(prefix.trim()) && !completionProposal.getDisplayString().toUpperCase().startsWith(prefix.toUpperCase()))) { - if (logger.isDebugEnabled()) { - logger.debug("filter completionProposal '" + completionProposal + "'"); - } - iterator.remove(); - } + // filter by prefix + if (isFiltered(model, prefix, completionProposal)) { + if (logger.isDebugEnabled()) { + logger.debug("filter completionProposal '" + completionProposal + "'"); } + iterator.remove(); } - } else { if (logger.isDebugEnabled()) { logger.debug("filter duplicate completionProposal '" + completionProposal + "'"); } - iterator.remove(); } } + Collections.sort(completionProposalList, PROPOSAL_COMPARATOR); + return completionProposalList; } /** - * @param text - * to apply + * The default behaviour of this method delegates to {@link XtextCompletionProposal#matches(String)} to + * test if the given prefix string matches or not. + * + * @param model the last semtantically complete object + * @param prefix + * @param completionProposal contains information used to present the proposed completion to the user + * @return true or false whether the given prefix matches the text of this completion proposal + */ + protected boolean isFiltered(EObject model, String prefix, ICompletionProposal completionProposal) { + + if (completionProposal instanceof XtextCompletionProposal) { + + XtextCompletionProposal xtextCompletionProposal = (XtextCompletionProposal) completionProposal; + + return !xtextCompletionProposal.matches(prefix); + } + + return false; + } + + /** + * @param text to apply * @return the provided string with the first letter capitalized */ protected final String firstLetterCapitalized(String text) { return Strings.toFirstUpper(text); } + + /** + * + * Simple {@link Comparator} implementation to compare + * <code>ICompletionProposal</code> by disaply strings'. + * + */ + private static final class ProposalComparator implements Comparator<ICompletionProposal> { + public int compare(ICompletionProposal o1, ICompletionProposal o2) { + return o1.getDisplayString().compareTo(o2.getDisplayString()); + } + } } diff --git a/plugins/org.eclipse.xtext.ui.common/src/org/eclipse/xtext/ui/common/editor/codecompletion/ContentAssistContextAdapter.java b/plugins/org.eclipse.xtext.ui.common/src/org/eclipse/xtext/ui/common/editor/codecompletion/ContentAssistContextAdapter.java new file mode 100644 index 0000000..615ca53 --- a/dev/null +++ b/plugins/org.eclipse.xtext.ui.common/src/org/eclipse/xtext/ui/common/editor/codecompletion/ContentAssistContextAdapter.java @@ -0,0 +1,150 @@ +/******************************************************************************* + * 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 org.eclipse.emf.common.notify.impl.AdapterImpl; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.xtext.GrammarUtil; +import org.eclipse.xtext.ParserRule; +import org.eclipse.xtext.parsetree.AbstractNode; +import org.eclipse.xtext.parsetree.CompositeNode; + +/** + * Represents a custom emf adapter to adapt or extend the behaviour of emf + * models used to provide CA related context information. + * + * @author Michael Clay - Initial contribution and API + * @see org.eclipse.emf.common.notify.Adapter + */ +public class ContentAssistContextAdapter extends AdapterImpl { + + private CompositeNode rootNode; + + private AbstractNode lastCompleteNode; + + private AbstractNode currentNode; + + private String prefix; + + private int offset; + + public ContentAssistContextAdapter(CompositeNode rootNode, AbstractNode currentNode, AbstractNode lastCompleteNode, + int offset, String prefix) { + super(); + this.rootNode = rootNode; + this.currentNode = currentNode; + this.lastCompleteNode = lastCompleteNode; + this.offset = offset; + this.prefix = prefix; + } + + /** + * @return true or false wheter the cursor is at the the last complete node + */ + public boolean isCusorAtEndOfLastCompleteNode() { + return lastCompleteNode == currentNode; + } + + /** + * @return the grammar element of the current node + */ + public EObject getCurrentGrammarElement() { + + EObject grammarElement = GrammarUtil.containingAssignment(currentNode.getGrammarElement()); + + if (null == grammarElement) { + grammarElement = (currentNode.getGrammarElement() instanceof ParserRule ? ((ParserRule) currentNode + .getGrammarElement()).getAlternatives() + : currentNode.getGrammarElement()); + } + + return grammarElement; + } + + @Override + public boolean isAdapterForType(Object type) { + return type == ContentAssistContextAdapter.class; + } + + /** + * @return the rootNode + */ + public CompositeNode getRootNode() { + return rootNode; + } + + /** + * @param rootNode + * the rootNode to set + */ + public void setRootNode(CompositeNode rootNode) { + this.rootNode = rootNode; + } + + /** + * @return the lastCompleteNode + */ + public AbstractNode getLastCompleteNode() { + return lastCompleteNode; + } + + /** + * @param lastCompleteNode + * the lastCompleteNode to set + */ + public void setLastCompleteNode(AbstractNode lastCompleteNode) { + this.lastCompleteNode = lastCompleteNode; + } + + /** + * @return the currentNode + */ + public AbstractNode getCurrentNode() { + return currentNode; + } + + /** + * @param currentNode + * the currentNode to set + */ + public void setCurrentNode(AbstractNode currentNode) { + this.currentNode = currentNode; + } + + /** + * @return the prefix + */ + public String getPrefix() { + return prefix; + } + + /** + * @param prefix + * the prefix to set + */ + public void setPrefix(String prefix) { + this.prefix = prefix; + } + + /** + * @return the offset + */ + public int getOffset() { + return offset; + } + + /** + * @param offset + * the offset to set + */ + public void setOffset(int offset) { + this.offset = offset; + } + +} 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 8a08ed6..698773c 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 @@ -7,8 +7,10 @@ import java.util.Set; import org.apache.log4j.Logger; import org.eclipse.core.runtime.Assert; +import org.eclipse.emf.common.notify.Adapter; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EReference; +import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.contentassist.ContextInformationValidator; @@ -23,6 +25,7 @@ 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.crossref.ILinkingService; import org.eclipse.xtext.parser.IParseResult; @@ -48,14 +51,10 @@ public class DefaultContentAssistProcessor implements IContentAssistProcessor { @Inject private IProposalProvider proposalProvider; + + @Inject + private ILinkingService linkingService; - /** - * @param proposalProvider - * the proposalProvider to set - */ - public void setProposalProvider(IProposalProvider proposalProvider) { - this.proposalProvider = proposalProvider; - } /** * computes the possible grammar elements following the one at the given offset and calls the respective methods on @@ -65,97 +64,122 @@ public class DefaultContentAssistProcessor implements IContentAssistProcessor { ICompletionProposal[] completionProposals = null; - if (proposalProvider != null) { - - IDocument document = viewer.getDocument(); + IDocument document = viewer.getDocument(); - if (document instanceof IXtextDocument) { + if (document instanceof IXtextDocument) { - List<ICompletionProposal> completionProposalList = new ArrayList<ICompletionProposal>(); + List<ICompletionProposal> completionProposalList = new ArrayList<ICompletionProposal>(); - IXtextDocument xtextDocument = (IXtextDocument) document; + IXtextDocument xtextDocument = (IXtextDocument) document; - CompositeNode rootNode = xtextDocument.readOnly(new UnitOfWork<CompositeNode>() { - public CompositeNode exec(XtextResource resource) throws Exception { - IParseResult parseResult = resource.getParseResult(); - Assert.isNotNull(parseResult); - return parseResult.getRootNode(); - } - }); - - Assert.isNotNull(rootNode); + CompositeNode rootNode = xtextDocument.readOnly(new UnitOfWork<CompositeNode>() { + public CompositeNode exec(XtextResource resource) throws Exception { + IParseResult parseResult = resource.getParseResult(); + Assert.isNotNull(parseResult); + return parseResult.getRootNode(); + } + }); - AbstractNode lastCompleteNode = ParseTreeUtil.getLastCompleteNodeByOffset(rootNode, offset); + Assert.isNotNull(rootNode); - LeafNode currentLeafNode = ParseTreeUtil.getCurrentNodeByOffset(rootNode, offset); + AbstractNode lastCompleteNode = ParseTreeUtil.getLastCompleteNodeByOffset(rootNode, offset); - String prefix = calculatePrefix(viewer, offset, currentLeafNode); + AbstractNode currentNode = ParseTreeUtil.getCurrentNodeByOffset(rootNode, offset); - EObject model = lastCompleteNode instanceof AbstractNode ? NodeUtil - .getNearestSemanticObject((AbstractNode) lastCompleteNode) : lastCompleteNode; + String prefix = calculatePrefix(viewer, offset, currentNode); - Set<AbstractElement> nextValidElementSet = new LinkedHashSet<AbstractElement>(); - /** - * in case of a crossreference which isnt linked already we evaluate it again and delegate to - * proposalProvider (again) - */ - if (lastCompleteNode.getGrammarElement() instanceof CrossReference && !isLinked(lastCompleteNode)) { - nextValidElementSet.add((AbstractElement) lastCompleteNode.getGrammarElement()); + EObject model = lastCompleteNode instanceof AbstractNode ? NodeUtil + .getNearestSemanticObject((AbstractNode) lastCompleteNode) : lastCompleteNode; + + addOrReplaceCaContextAdapter(model, new ContentAssistContextAdapter(rootNode,currentNode,lastCompleteNode,offset,prefix)); + + Set<AbstractElement> nextValidElementSet = new LinkedHashSet<AbstractElement>(); + /** + * in case of a crossreference which isnt linked properly we evaluate or propose it again + */ + if (lastCompleteNode.getGrammarElement() instanceof CrossReference && !isLinked(lastCompleteNode)) { + nextValidElementSet.add(getAbstractElement(lastCompleteNode)); + nextValidElementSet.addAll(ParseTreeUtil.getElementSetValidFromOffset(rootNode, lastCompleteNode, + offset)); + } + /** + * in case of 'at-the-end' of the previous,completed element we evaluate it again for + * 'right-to-left-backtracking' cases (e.g. for keyword 'kind' kind>|< |=cursorpos) + */ + else if (lastCompleteNode == currentNode) { + + Assignment containingAssignment = GrammarUtil + .containingAssignment(lastCompleteNode.getGrammarElement()); + + if (lastCompleteNode.getGrammarElement() instanceof RuleCall && containingAssignment != null) { + nextValidElementSet.add(containingAssignment); nextValidElementSet.addAll(ParseTreeUtil.getElementSetValidFromOffset(rootNode, lastCompleteNode, offset)); } - /** - * in case of 'at-the-end' of the previous,completed element we evaluate it again for - * 'right-to-left-backtracking' cases (e.g. for keyword 'kind' kind>|< |=cursorpos) - */ - else if (currentLeafNode == lastCompleteNode) { - Assignment containingAssignment = GrammarUtil.containingAssignment(lastCompleteNode.getGrammarElement()); - - if (lastCompleteNode.getGrammarElement() instanceof RuleCall && containingAssignment!=null) { - nextValidElementSet.add(containingAssignment); - nextValidElementSet.addAll(ParseTreeUtil.getElementSetValidFromOffset(rootNode, lastCompleteNode, offset)); - } else { - nextValidElementSet = ParseTreeUtil.getElementSetValidFromOffset(rootNode, lastCompleteNode, offset); - nextValidElementSet.add((AbstractElement) lastCompleteNode.getGrammarElement()); - } - } else { - nextValidElementSet = ParseTreeUtil - .getElementSetValidFromOffset(rootNode, lastCompleteNode, offset); + nextValidElementSet = ParseTreeUtil.getElementSetValidFromOffset(rootNode, lastCompleteNode, offset); + nextValidElementSet.add(getAbstractElement(lastCompleteNode)); } + } + else { + nextValidElementSet = ParseTreeUtil.getElementSetValidFromOffset(rootNode, lastCompleteNode, offset); + } - ProposalProviderInvokerSwitch proposalProviderInvokerSwitch = new ProposalProviderInvokerSwitch(model, - document, offset, prefix, proposalProvider); + ProposalProviderInvokerSwitch proposalProviderInvokerSwitch = new ProposalProviderInvokerSwitch(model, + document, offset, prefix, proposalProvider); - for (List<EObject> resolvedElementOrRuleList : new ProposalCandidateResolverSwitch(nextValidElementSet)) { + for (List<EObject> resolvedElementOrRuleList : new ProposalCandidateResolverSwitch(nextValidElementSet)) { - List<ICompletionProposal> collectedCompletionProposalList = proposalProviderInvokerSwitch - .collectCompletionProposalList(resolvedElementOrRuleList); + List<ICompletionProposal> collectedCompletionProposalList = proposalProviderInvokerSwitch + .collectCompletionProposalList(resolvedElementOrRuleList); - completionProposalList.addAll(collectedCompletionProposalList); - } - if (completionProposalList != null) { - List<? extends ICompletionProposal> processedCompletionProposalList = proposalProvider - .sortAndFilter(completionProposalList, model, prefix, document, offset); - completionProposals = processedCompletionProposalList.toArray(new ICompletionProposal[] {}); - } + completionProposalList.addAll(collectedCompletionProposalList); + } + + if (completionProposalList != null) { + List<? extends ICompletionProposal> processedCompletionProposalList = proposalProvider.sortAndFilter( + completionProposalList, model, prefix, document, offset); + completionProposals = processedCompletionProposalList.toArray( + new ICompletionProposal[processedCompletionProposalList.size()]); } + } return completionProposals; } - protected String calculatePrefix(ITextViewer viewer, final int offset, LeafNode currentLeafNode) { + + public char[] getCompletionProposalAutoActivationCharacters() { + return null; + } + + public String getErrorMessage() { + return null; + } + + public IContextInformation[] computeContextInformation(ITextViewer viewer, int offset) { + return null; + } - if (currentLeafNode == null) + public char[] getContextInformationAutoActivationCharacters() { + return null; + } + + public IContextInformationValidator getContextInformationValidator() { + return new ContextInformationValidator(this); + } + + protected String calculatePrefix(ITextViewer viewer, final int offset, AbstractNode abstractNode) { + + if (abstractNode == null) return ""; String prefix = ""; StyledText textWidget = viewer.getTextWidget(); if (textWidget.getCharCount() > 0) { int boundedOffset = Math.min(offset, textWidget.getCharCount()) - 1; - if (currentLeafNode.getTotalOffset() <= boundedOffset) - prefix = textWidget.getText(currentLeafNode.getTotalOffset(), boundedOffset); + if (abstractNode.getTotalOffset() <= boundedOffset) + prefix = textWidget.getText(abstractNode.getTotalOffset(), boundedOffset); } // if cursor is behind a complete keyword, accept any input => empty @@ -163,36 +187,29 @@ public class DefaultContentAssistProcessor implements IContentAssistProcessor { // TODO: Find a way to distinguish between keywords like "+" or "-" and // "extends" or "class" // in the latter case, the prefix "" would not always be sufficient - if (currentLeafNode.getGrammarElement() instanceof Keyword && currentLeafNode.getText().equals(prefix)) { + if (abstractNode.getGrammarElement() instanceof Keyword && (abstractNode instanceof LeafNode && ((LeafNode)abstractNode).getText().equals(prefix))) { prefix = ""; } return prefix; } - public char[] getCompletionProposalAutoActivationCharacters() { - return null; - } - - public String getErrorMessage() { - return null; - } + + private void addOrReplaceCaContextAdapter(EObject model, ContentAssistContextAdapter contentAssistContextAdapter) { + + if (model != null) { - public IContextInformation[] computeContextInformation(ITextViewer viewer, int offset) { - return null; - } + Adapter existingAdapter = EcoreUtil.getAdapter(model.eAdapters(), ContentAssistContextAdapter.class); - public char[] getContextInformationAutoActivationCharacters() { - return null; - } + if (existingAdapter != null) { + model.eAdapters().remove(existingAdapter); + } - public IContextInformationValidator getContextInformationValidator() { - return new ContextInformationValidator(this); + model.eAdapters().add(contentAssistContextAdapter); + } } - @Inject - private ILinkingService linkingService; - + private boolean isLinked(AbstractNode lastCompleteNode) { EObject semanticModel = NodeUtil.getNearestSemanticObject(lastCompleteNode); CrossReference crossReference = (CrossReference) lastCompleteNode.getGrammarElement(); @@ -208,5 +225,10 @@ public class DefaultContentAssistProcessor implements IContentAssistProcessor { return !linkCandidates.isEmpty() && referencedObjects.containsAll(linkCandidates); } } + + private AbstractElement getAbstractElement(AbstractNode lastCompleteNode) { + return (AbstractElement) (lastCompleteNode.getGrammarElement() instanceof ParserRule ? + ((ParserRule)lastCompleteNode.getGrammarElement()).getAlternatives(): lastCompleteNode.getGrammarElement()); + } } 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 58dc0d1..1e3228a 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 @@ -3,6 +3,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.emf.ecore.util.EcoreUtil; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.DocumentEvent; @@ -20,11 +21,10 @@ import org.eclipse.xtext.AbstractElement; import org.eclipse.xtext.Assignment; import org.eclipse.xtext.CrossReference; import org.eclipse.xtext.GrammarUtil; +import org.eclipse.xtext.Keyword; import org.eclipse.xtext.RuleCall; -import org.eclipse.xtext.parsetree.CompositeNode; +import org.eclipse.xtext.parsetree.AbstractNode; import org.eclipse.xtext.parsetree.LeafNode; -import org.eclipse.xtext.parsetree.NodeUtil; -import org.eclipse.xtext.parsetree.ParseTreeUtil; /** * Default Xtext implementation of interface <code>ICompletionProposal</code>. @@ -54,19 +54,13 @@ public class XtextCompletionProposal implements ICompletionProposal, * @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 - * the label to be displayed - * @param description - * some additional description for the tooltip - * @param imageFilePath - * the relative path of the image file, relative to the root of + * @param text the text value to be replaced/inserted + * @param label the label to be displayed + * @param description some additional description for the tooltip + * @param imageFilePath the relative path of the image file, relative to the root of * the plug-in; the path must be legal - * @param pluginId - * the id of the plug-in containing the image file; - * @param offset - * the offset of the text + * @param pluginId the id of the plug-in containing the image file; + * @param offset the offset of the text */ public XtextCompletionProposal(AbstractElement element,EObject model,String text, StyledString label, String description, String imageFilePath, String pluginIdentifier, @@ -217,47 +211,38 @@ public class XtextCompletionProposal implements ICompletionProposal, IDocument document = viewer.getDocument(); - int offsetToApply = this.offset; - if (model != null) { + ContentAssistContextAdapter contentAssistContextAdapter = getContextAdapater(); - CompositeNode parserNode = NodeUtil.getRootNode(model); - - LeafNode currentLeafNode=ParseTreeUtil.getCurrentNodeByOffset(parserNode, offset); - - boolean isCursorAtTheEndOfTheLastElement = offset == (currentLeafNode.getTotalOffset() + currentLeafNode - .getTotalLength()); + AbstractNode abstractNode = contentAssistContextAdapter.getCurrentNode(); - if ((currentLeafNode.isHidden() && !"".equals(currentLeafNode.getText().trim())) - || isCursorAtTheEndOfTheLastElement) { + if (abstractNode instanceof LeafNode) { + + LeafNode currentLeafNode = (LeafNode) abstractNode; + if (getDisplayString().toUpperCase().startsWith(currentLeafNode.getText().toUpperCase())) { - offsetToApply-=currentLeafNode.getText().trim().length(); - } - } - - if (!currentLeafNode.isHidden() && - isCursorAtTheEndOfTheLastElement && - offsetToApply==offset) { + setText(getText().substring(this.offset - currentLeafNode.getTotalOffset())); + } else if (contentAssistContextAdapter.isCusorAtEndOfLastCompleteNode()) { - if (currentLeafNode.getGrammarElement() instanceof CrossReference - && abstractElement instanceof CrossReference) { - setText(" " + getText()); + if (currentLeafNode.getGrammarElement() instanceof CrossReference + && abstractElement instanceof CrossReference) { + setText(" " + getText()); + } + else if (currentLeafNode.getGrammarElement() instanceof RuleCall + && currentLeafNode.getGrammarElement().eContainer() instanceof Assignment + && abstractElement instanceof Assignment) { + setText(" " + getText()); + } + else if (!GrammarUtil.containingParserRule(abstractElement).equals( + GrammarUtil.containingParserRule(currentLeafNode.getGrammarElement()))) { + setText(" " + getText()); + } } - else if (currentLeafNode.getGrammarElement() instanceof RuleCall - && currentLeafNode.getGrammarElement().eContainer() instanceof Assignment - && abstractElement instanceof Assignment) { - setText(" " + getText()); - } - else if (!GrammarUtil.containingParserRule(abstractElement).equals(GrammarUtil.containingParserRule(currentLeafNode.getGrammarElement()))) { - setText(" " + getText()); - } - - } + } } - document.replace(offsetToApply, offset != offsetToApply ? offset - - offsetToApply : 0, getText()); + document.replace(this.offset, document.getLength()<(this.offset+getText().length()) ? 0:getText().length() , getText()); } catch (BadLocationException e) { logger.error(e); @@ -325,6 +310,50 @@ public class XtextCompletionProposal implements ICompletionProposal, return new Point(offset + this.text.length(), 0); } + /** + * + * @param prefix to match + * @return true or false whether the given prefix matches the text of this completion proposal + */ + public boolean matches(String prefix) { + + boolean matches = true; + + if (model != null) { + + AbstractElement abstractElement = null; + + if (getAbstractElement() instanceof Keyword || + getAbstractElement() instanceof CrossReference) { + abstractElement = GrammarUtil.containingAssignment(getAbstractElement()); + } + + if (null==abstractElement) { + abstractElement = getAbstractElement(); + } + + ContentAssistContextAdapter contentAssistContextAdapter = getContextAdapater(); + + boolean candidateToCompare = false; + + // means if we are at the end of a complete token we want to filter only equal grammarelements (not the 'next' ones) + if (contentAssistContextAdapter.isCusorAtEndOfLastCompleteNode() && + abstractElement.equals(contentAssistContextAdapter.getCurrentGrammarElement())) { + candidateToCompare = true; + } else if (!contentAssistContextAdapter.isCusorAtEndOfLastCompleteNode() ) { + candidateToCompare = true; + } + + if ( candidateToCompare && (!"".equals(prefix.trim()) && + !getDisplayString().toUpperCase().trim().startsWith(prefix.toUpperCase().trim()))) { + matches = false; + } + } + + + return matches; + } + @Override public String toString() { return "XtextCompletionPoposal[text='"+getText()+"']"; @@ -344,4 +373,10 @@ public class XtextCompletionProposal implements ICompletionProposal, // } this.image = newImage; } + + private ContentAssistContextAdapter getContextAdapater() { + ContentAssistContextAdapter contentAssistContextAdapter = (ContentAssistContextAdapter) + EcoreUtil.getAdapter(model.eAdapters(), ContentAssistContextAdapter.class); + return contentAssistContextAdapter; + } } diff --git a/tests/org.eclipse.xtext.generator.tests/src/org/eclipse/xtext/parsetree/ParseTreeUtilTest.java b/tests/org.eclipse.xtext.generator.tests/src/org/eclipse/xtext/parsetree/ParseTreeUtilTest.java index 73875c1..cf75fe1 100644 --- a/tests/org.eclipse.xtext.generator.tests/src/org/eclipse/xtext/parsetree/ParseTreeUtilTest.java +++ b/tests/org.eclipse.xtext.generator.tests/src/org/eclipse/xtext/parsetree/ParseTreeUtilTest.java @@ -61,27 +61,27 @@ public class ParseTreeUtilTest extends AbstractGeneratorTest { String text = "spielplatz 1 \"junit\" { kin "; CompositeNode rootNode = getRootNode(text); - LeafNode currentNodeByOffset = ParseTreeUtil.getCurrentNodeByOffset(rootNode, 26); + LeafNode currentNodeByOffset = (LeafNode) ParseTreeUtil.getCurrentNodeByOffset(rootNode, 26); assertEquals("expect leafnode with text 'kin'", currentNodeByOffset.getText(), "kin"); text = "spielplatz 1 \"junit\" { kind ("; rootNode = getRootNode(text); - currentNodeByOffset = ParseTreeUtil.getCurrentNodeByOffset(rootNode, text.length()); + currentNodeByOffset = (LeafNode) ParseTreeUtil.getCurrentNodeByOffset(rootNode, text.length()); assertEquals("expect leafnode with text '('", currentNodeByOffset.getText(), "("); text = "spielplatz 1 \"junit\" { kind ( "; rootNode = getRootNode(text); - currentNodeByOffset = ParseTreeUtil.getCurrentNodeByOffset(rootNode, text.length()); + currentNodeByOffset = (LeafNode) ParseTreeUtil.getCurrentNodeByOffset(rootNode, text.length()); assertEquals("expect leafnode with WS text '__'", currentNodeByOffset.getText(), " "); text = "spielplatz 1 \"junit\" { kind ("; rootNode = getRootNode(text); - currentNodeByOffset = ParseTreeUtil.getCurrentNodeByOffset(rootNode, text.length() - 1); - assertEquals("expect leafnode with WS text '_'", currentNodeByOffset.getText(), " "); + currentNodeByOffset = (LeafNode) ParseTreeUtil.getCurrentNodeByOffset(rootNode, text.length() - 1); + assertEquals("expect leafnode with '('", currentNodeByOffset.getText(), "("); text = "spielplatz 1 \"junit\" { kind ("; rootNode = getRootNode(text); - currentNodeByOffset = ParseTreeUtil.getCurrentNodeByOffset(rootNode, 0); + currentNodeByOffset = (LeafNode) ParseTreeUtil.getCurrentNodeByOffset(rootNode, 0); assertEquals("expect leafnode with WS text 'spielplatz'", currentNodeByOffset.getText(), "spielplatz"); } diff --git a/tests/org.eclipse.xtext.ui.common.tests/src/org/eclipse/xtext/ui/common/editor/outline/OutlineViewTest.java b/tests/org.eclipse.xtext.ui.common.tests/src/org/eclipse/xtext/ui/common/editor/outline/OutlineViewTest.java index 99a955a..ababcdc 100644 --- a/tests/org.eclipse.xtext.ui.common.tests/src/org/eclipse/xtext/ui/common/editor/outline/OutlineViewTest.java +++ b/tests/org.eclipse.xtext.ui.common.tests/src/org/eclipse/xtext/ui/common/editor/outline/OutlineViewTest.java @@ -28,6 +28,7 @@ import org.eclipse.ui.views.contentoutline.ContentOutline; import org.eclipse.ui.views.contentoutline.IContentOutlinePage; import org.eclipse.xtext.EcoreUtil2; import org.eclipse.xtext.parser.IParseResult; +import org.eclipse.xtext.parsetree.AbstractNode; import org.eclipse.xtext.parsetree.CompositeNode; import org.eclipse.xtext.parsetree.LeafNode; import org.eclipse.xtext.parsetree.ParseTreeUtil; @@ -177,7 +178,7 @@ public class OutlineViewTest extends AbstractEditorTest { assertSynchronized(editor, 2, 45, 0); } - protected LeafNode getCurrentEditorNode() { + protected AbstractNode getCurrentEditorNode() { XtextDocument document = (XtextDocument) editor.getDocument(); ITextSelection selection = (ITextSelection) editor.getSelectionProvider().getSelection(); @@ -195,8 +196,7 @@ public class OutlineViewTest extends AbstractEditorTest { }); Assert.isNotNull(rootNode); - LeafNode currentNodeByOffset = ParseTreeUtil.getCurrentNodeByOffset(rootNode, offset); - return currentNodeByOffset; + return ParseTreeUtil.getCurrentNodeByOffset(rootNode, offset); } protected void assertSynchronized(XtextEditor editor, int elementIndex, int offset, int length) { @@ -239,9 +239,12 @@ public class OutlineViewTest extends AbstractEditorTest { EObject objInEditor = contents.get(elementIndex); // just debugging purposes - LeafNode currentEditorNode = getCurrentEditorNode(); - System.out.println("Selection [" + offset + ";" + length + "] yields node text [" - + currentEditorNode.getText() + "]"); + AbstractNode currentEditorNode = getCurrentEditorNode(); + if (currentEditorNode instanceof LeafNode) { + System.out.println("Selection [" + offset + ";" + length + "] yields node text [" + + ((LeafNode)currentEditorNode).getText() + "]"); + } + // obtain selected model element in outline EObject objInOutline = resource.getEObject(uri.fragment()); |

