/******************************************************************************* * Copyright (c) 2000, 2015 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * Anton Leherbauer (Wind River Systems) * Bryan Wilkinson (QNX) * Markus Schorn (Wind River Systems) * Kirk Beitz (Nokia) *******************************************************************************/ package org.eclipse.cdt.internal.ui.text.contentassist; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.InvalidRegistryObjectException; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.contentassist.ContentAssistant; import org.eclipse.jface.text.contentassist.ICompletionProposal; import org.eclipse.jface.text.contentassist.IContextInformation; import org.eclipse.jface.text.contentassist.IContextInformationValidator; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.ui.IEditorPart; import org.eclipse.cdt.core.dom.ast.IASTCompletionNode; import org.eclipse.cdt.core.dom.ast.IASTExpression; import org.eclipse.cdt.core.dom.ast.IASTFieldReference; import org.eclipse.cdt.core.dom.ast.IASTName; import org.eclipse.cdt.core.dom.ast.IPointerType; import org.eclipse.cdt.core.dom.ast.IType; import org.eclipse.cdt.ui.CUIPlugin; import org.eclipse.cdt.ui.text.ICCompletionProposal; import org.eclipse.cdt.ui.text.contentassist.ContentAssistInvocationContext; import org.eclipse.cdt.ui.text.contentassist.IProposalFilter; import org.eclipse.cdt.internal.core.dom.parser.cpp.ICPPUnknownType; import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.HeuristicResolver; import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.SemanticUtil; import org.eclipse.cdt.internal.ui.preferences.ProposalFilterPreferencesUtil; import org.eclipse.cdt.internal.ui.text.CHeuristicScanner; import org.eclipse.cdt.internal.ui.text.CParameterListValidator; import org.eclipse.cdt.internal.ui.text.Symbols; /** * C/C++ content assist processor. */ public class CContentAssistProcessor extends ContentAssistProcessor { private static class ActivationSet { private final String set; ActivationSet(String s) { set = s; } boolean contains(char c) { return -1 != set.indexOf(c); } } /** * A wrapper for {@link ICompetionProposal}s. */ private static class CCompletionProposalWrapper implements ICCompletionProposal { private ICompletionProposal fWrappedProposal; public CCompletionProposalWrapper(ICompletionProposal proposal) { fWrappedProposal= proposal; } @Override public String getIdString() { return fWrappedProposal.getDisplayString(); } @Override public int getRelevance() { return RelevanceConstants.CASE_MATCH_RELEVANCE + RelevanceConstants.KEYWORD_TYPE_RELEVANCE; } @Override public void apply(IDocument document) { throw new UnsupportedOperationException(); } @Override public String getAdditionalProposalInfo() { return fWrappedProposal.getAdditionalProposalInfo(); } @Override public IContextInformation getContextInformation() { return fWrappedProposal.getContextInformation(); } @Override public String getDisplayString() { return fWrappedProposal.getDisplayString(); } @Override public Image getImage() { return fWrappedProposal.getImage(); } @Override public Point getSelection(IDocument document) { return fWrappedProposal.getSelection(document); } /** * @return the original proposal */ public ICompletionProposal unwrap() { return fWrappedProposal; } } private ActivationSet fReplacementAutoActivationCharacters; private ActivationSet fCContentAutoActivationCharacters; private IContextInformationValidator fValidator; private final IEditorPart fEditor; public CContentAssistProcessor(IEditorPart editor, ContentAssistant assistant, String partition) { super(assistant, partition); fEditor= editor; } @Override public IContextInformationValidator getContextInformationValidator() { if (fValidator == null) { fValidator= new CParameterListValidator(); } return fValidator; } @Override protected List<ICompletionProposal> filterAndSortProposals(List<ICompletionProposal> proposals, IProgressMonitor monitor, ContentAssistInvocationContext context) { IProposalFilter filter = getCompletionFilter(); ICCompletionProposal[] proposalsInput= new ICCompletionProposal[proposals.size()]; // Wrap proposals which are no ICCompletionProposals. boolean wrapped= false; int i= 0; for (ICompletionProposal proposal : proposals) { if (proposal instanceof ICCompletionProposal) { proposalsInput[i++]= (ICCompletionProposal) proposal; } else { wrapped= true; proposalsInput[i++]= new CCompletionProposalWrapper(proposal); } } // Filter. ICCompletionProposal[] proposalsFiltered = filter.filterProposals(proposalsInput); // Sort. boolean sortByAlphabet= CUIPlugin.getDefault().getPreferenceStore().getBoolean(ContentAssistPreference.ORDER_PROPOSALS); if (sortByAlphabet) { // Already sorted alphabetically by DefaultProposalFilter // in case of custom proposal filter, keep ordering applied by filter. } else { // Sort by relevance. CCompletionProposalComparator propsComp= new CCompletionProposalComparator(); propsComp.setOrderAlphabetically(sortByAlphabet); Arrays.sort(proposalsFiltered, propsComp); } List<ICompletionProposal> filteredList; if (wrapped) { // Unwrap again. filteredList= new ArrayList<>(proposalsFiltered.length); for (ICCompletionProposal proposal : proposalsFiltered) { if (proposal instanceof CCompletionProposalWrapper) { filteredList.add(((CCompletionProposalWrapper)proposal).unwrap()); } else { filteredList.add(proposal); } } } else { final ICompletionProposal[] tmp= proposalsFiltered; filteredList= Arrays.asList(tmp); } return filteredList; } private IProposalFilter getCompletionFilter() { IProposalFilter filter = null; try { IConfigurationElement filterElement = ProposalFilterPreferencesUtil.getPreferredFilterElement(); if (null != filterElement) { Object contribObject = filterElement.createExecutableExtension("class"); //$NON-NLS-1$ if ((contribObject instanceof IProposalFilter)) { filter = (IProposalFilter) contribObject; } } } catch (InvalidRegistryObjectException e) { // No action required since we will be using the fail-safe default filter. CUIPlugin.log(e); } catch (CoreException e) { // No action required since we will be using the fail-safe default filter. CUIPlugin.log(e); } if (filter == null) { // Fail-safe default implementation. filter = new DefaultProposalFilter(); } return filter; } @Override protected List<IContextInformation> filterAndSortContextInformation(List<IContextInformation> contexts, IProgressMonitor monitor) { return contexts; } /** * Establishes this processor's set of characters checked after * auto-activation to determine if auto-replacement correction * is to occur. * <p> * This set is a (possibly complete) subset of the set established by * {@link ContentAssistProcessor#setCompletionProposalAutoActivationCharacters}, * which is the set of characters used to initially trigger auto-activation * for any content-assist operations, including this. (<i>And while the * name setCompletionProposalAutoActivationCharacters may now be a bit * misleading, it is part of an API implementation called by jface.</i>) * * @param activationSet the activation set */ public void setReplacementAutoActivationCharacters(String activationSet) { fReplacementAutoActivationCharacters= new ActivationSet(activationSet); } /** * Establishes this processor's set of characters checked after * auto-activation and any auto-correction to determine if completion * proposal computation is to proceed. * <p> * This set is a (possibly complete) subset of the set established by * {@link ContentAssistProcessor#setCompletionProposalAutoActivationCharacters}, * which is the set of characters used to initially trigger auto-activation * for any content-assist operations, including this. (<i>And while the * name setCompletionProposalAutoActivationCharacters may now be a bit * misleading, it is part of an API implementation called by jface.</i>) * * @param activationSet the activation set */ public void setCContentAutoActivationCharacters(String activationSet) { fCContentAutoActivationCharacters= new ActivationSet(activationSet); } @Override protected ContentAssistInvocationContext createContext(ITextViewer viewer, int offset, boolean isCompletion) { char activationChar = getActivationChar(viewer, offset); CContentAssistInvocationContext context = new CContentAssistInvocationContext(viewer, offset, fEditor, isCompletion, isAutoActivated()); if (isCompletion && activationChar == '.' && fReplacementAutoActivationCharacters != null && fReplacementAutoActivationCharacters.contains('.')) { IASTCompletionNode node = context.getCompletionNode(); if (node != null) { IASTName[] names = node.getNames(); if (names.length > 0 && names[0].getParent() instanceof IASTFieldReference) { IASTFieldReference ref = (IASTFieldReference) names[0].getParent(); IASTExpression ownerExpr = ref.getFieldOwner(); IType ownerExprType = SemanticUtil.getNestedType(ownerExpr.getExpressionType(), SemanticUtil.TDEF); if (ownerExprType instanceof ICPPUnknownType) { ownerExprType = HeuristicResolver.resolveUnknownType((ICPPUnknownType) ownerExprType, names[0]); } if (ownerExprType instanceof IPointerType) { context = replaceDotWithArrow(viewer, offset, isCompletion, context, activationChar); } } } if (context != null && isAutoActivated() && !fCContentAutoActivationCharacters.contains(activationChar)) { // Auto-replace, but not auto-content-assist - bug 344387. context.dispose(); context = null; } } return context; } private CContentAssistInvocationContext replaceDotWithArrow(ITextViewer viewer, int offset, boolean isCompletion, CContentAssistInvocationContext context, char activationChar) { IDocument doc = viewer.getDocument(); try { doc.replace(offset - 1, 1, "->"); //$NON-NLS-1$ context.dispose(); context = null; // If user turned on activation only for replacement characters, // setting the context to null will skip the proposals popup later. if (!isAutoActivated() || fCContentAutoActivationCharacters.contains(activationChar)) { context = new CContentAssistInvocationContext(viewer, offset + 1, fEditor, isCompletion, isAutoActivated()); } } catch (BadLocationException e) { // Ignore } return context; } /** * Returns the character preceding the content assist activation offset. * * @param viewer * @param offset * @return the activation character */ private char getActivationChar(ITextViewer viewer, int offset) { IDocument doc= viewer.getDocument(); if (doc == null) { return 0; } if (offset <= 0) { return 0; } try { return doc.getChar(offset - 1); } catch (BadLocationException e) { } return 0; } @Override protected boolean verifyAutoActivation(ITextViewer viewer, int offset) { IDocument doc= viewer.getDocument(); if (doc == null) { return false; } if (offset <= 0) { return false; } try { char activationChar= doc.getChar(--offset); switch (activationChar) { case ':': return offset > 0 && doc.getChar(--offset) == ':'; case '>': return offset > 0 && doc.getChar(--offset) == '-'; case '.': // Avoid completion of float literals CHeuristicScanner scanner= new CHeuristicScanner(doc); int token= scanner.previousToken(--offset, Math.max(0, offset - 200)); // The scanner reports numbers as identifiers if (token == Symbols.TokenIDENT && !Character.isJavaIdentifierStart(doc.getChar(scanner.getPosition() + 1))) { // Not a valid identifier return false; } return true; } } catch (BadLocationException e) { } return false; } }