/******************************************************************************* * Copyright (c) 2005, 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) * Thomas Corbat (IFS) * Sergey Prigogin (Google) *******************************************************************************/ package org.eclipse.cdt.internal.ui.text.contentassist; import java.util.Map; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.CoreException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.ITextViewer; import org.eclipse.ui.IEditorPart; import org.eclipse.cdt.core.CCorePlugin; import org.eclipse.cdt.core.dom.ast.IASTCompletionNode; import org.eclipse.cdt.core.formatter.DefaultCodeFormatterConstants; import org.eclipse.cdt.core.index.IIndex; import org.eclipse.cdt.core.index.IIndexManager; import org.eclipse.cdt.core.model.ICProject; import org.eclipse.cdt.core.model.ITranslationUnit; import org.eclipse.cdt.ui.CUIPlugin; import org.eclipse.cdt.ui.PreferenceConstants; import org.eclipse.cdt.ui.text.contentassist.ContentAssistInvocationContext; import org.eclipse.cdt.ui.text.contentassist.ICEditorContentAssistInvocationContext; import org.eclipse.cdt.internal.ui.editor.CEditor; import org.eclipse.cdt.internal.ui.text.CHeuristicScanner; import org.eclipse.cdt.internal.ui.text.Symbols; /** * Describes the context of a content assist invocation in a C/C++ editor. * <p> * Clients may instantiate. A client that created a context is responsible for its disposal. * </p> * * @since 4.0 */ public class CContentAssistInvocationContext extends ContentAssistInvocationContext implements ICEditorContentAssistInvocationContext { private final IEditorPart fEditor; private final boolean fIsCompletion; private final boolean fIsAutoActivated; private IIndex fIndex; private Lazy<Integer> fContextInfoPosition = new Lazy<Integer>() { @Override protected Integer calculateValue() { return guessContextInformationPosition(); } }; private final Lazy<ITranslationUnit> fTU; private final Lazy<Integer> fParseOffset = new Lazy<Integer>() { @Override protected Integer calculateValue() { if (fIsCompletion) { return guessCompletionPosition(getInvocationOffset()); } int contextInfoPosition = fContextInfoPosition.value(); if (contextInfoPosition > 0) { return guessCompletionPosition(contextInfoPosition); } return -1; } }; private final Lazy<IASTCompletionNode> fCN = new Lazy<IASTCompletionNode>() { @Override protected IASTCompletionNode calculateValue() { int offset = getParseOffset(); if (offset < 0) return null; ICProject proj= getProject(); if (proj == null) return null; try { if (fIndex != null) throw new IllegalStateException("The method should not be called multiple times."); //$NON-NLS-1$ IIndexManager manager= CCorePlugin.getIndexManager(); fIndex = manager.getIndex(proj, IIndexManager.ADD_DEPENDENCIES | IIndexManager.ADD_EXTENSION_FRAGMENTS_CONTENT_ASSIST); try { fIndex.acquireReadLock(); } catch (InterruptedException e) { fIndex = null; } boolean parseNonIndexed= CUIPlugin.getDefault().getPreferenceStore().getBoolean(PreferenceConstants.PREF_USE_STRUCTURAL_PARSE_MODE); int flags = parseNonIndexed ? ITranslationUnit.AST_SKIP_INDEXED_HEADERS : ITranslationUnit.AST_SKIP_ALL_HEADERS; flags |= ITranslationUnit.AST_CONFIGURE_USING_SOURCE_CONTEXT; return fTU.value().getCompletionNode(fIndex, flags, offset); } catch (CoreException e) { CUIPlugin.log(e); } return null; } }; private final Lazy<Boolean> afterOpeningAngleBracket = new Lazy<Boolean>() { @Override protected Boolean calculateValue() { final int parseOffset = getParseOffset(); final int invocationOffset = getInvocationOffset(); final CHeuristicScanner scanner = new CHeuristicScanner(getDocument()); final int parenthesisOffset = scanner.findOpeningPeer(invocationOffset, parseOffset, '<', '>'); return parenthesisOffset != CHeuristicScanner.NOT_FOUND; } }; private final Lazy<Boolean> afterOpeningParenthesis = new Lazy<Boolean>() { @Override protected Boolean calculateValue() { final int invocationOffset = getInvocationOffset(); final int parseOffset = getParseOffset(); final int bound = Math.max(-1, parseOffset - 1); final CHeuristicScanner scanner = new CHeuristicScanner(getDocument()); final int parenthesisOffset = scanner.findOpeningPeer(invocationOffset, bound, '(', ')'); return parenthesisOffset != CHeuristicScanner.NOT_FOUND; } }; private final Lazy<Boolean> inUsingDeclaration = new Lazy<Boolean>() { /** * Checks whether the invocation offset is inside a using-declaration. * * @return {@code true} if the invocation offset is inside a using-declaration */ @Override protected Boolean calculateValue() { IDocument doc = getDocument(); int offset = Math.max(0, getInvocationOffset() - 1); // Look at the tokens preceding the invocation offset. CHeuristicScanner.TokenStream tokenStream = new CHeuristicScanner.TokenStream(doc, offset); int token = tokenStream.previousToken(); // There may be a partially typed identifier which is being completed. if (token == Symbols.TokenIDENT) token = tokenStream.previousToken(); // Before that, there may be any number of "namespace::" token pairs. while (token == Symbols.TokenDOUBLECOLON) { token = tokenStream.previousToken(); if (token == Symbols.TokenUSING) { // there could also be a leading "::" for global namespace return true; } else if (token != Symbols.TokenIDENT) { return false; } else { token = tokenStream.previousToken(); } } // Before that, there must be a "using" token. return token == Symbols.TokenUSING; } }; private final Lazy<Boolean> followedBySemicolon = new Lazy<Boolean>() { @Override protected Boolean calculateValue() { final IDocument doc = getDocument(); final int offset = getInvocationOffset(); final CHeuristicScanner.TokenStream tokenStream = new CHeuristicScanner.TokenStream(doc, offset); final int token = tokenStream.nextToken(); return token == Symbols.TokenSEMICOLON; } }; private final Lazy<String> functionParameterDelimiter = new Lazy<String>() { @Override protected String calculateValue() { String propertyKey = DefaultCodeFormatterConstants.FORMATTER_INSERT_SPACE_AFTER_COMMA_IN_METHOD_DECLARATION_PARAMETERS; Map<String, String> options = getProject().getOptions(true); return options.get(propertyKey).equals(CCorePlugin.INSERT) ? ", " : ","; //$NON-NLS-1$ //$NON-NLS-2$ } }; private final Lazy<String> templateParameterDelimiter = new Lazy<String>() { @Override protected String calculateValue() { String propertyKey = DefaultCodeFormatterConstants.FORMATTER_INSERT_SPACE_AFTER_COMMA_IN_TEMPLATE_PARAMETERS; Map<String, String> options = getProject().getOptions(true); return options.get(propertyKey).equals(CCorePlugin.INSERT) ? ", " : ","; //$NON-NLS-1$ //$NON-NLS-2$ } }; /** * Creates a new context. * * @param viewer the viewer used by the editor * @param offset the invocation offset * @param editor the editor that content assist is invoked in * @param isAutoActivated indicates whether content assist was auto-activated */ public CContentAssistInvocationContext(ITextViewer viewer, int offset, IEditorPart editor, boolean isCompletion, boolean isAutoActivated) { super(viewer, offset); Assert.isNotNull(editor); fEditor= editor; fIsCompletion= isCompletion; fIsAutoActivated= isAutoActivated; fTU = new Lazy<ITranslationUnit>() { @Override protected ITranslationUnit calculateValue() { return CUIPlugin.getDefault().getWorkingCopyManager().getWorkingCopy(fEditor.getEditorInput()); } }; } /** * Creates a new context. * * @param unit the translation unit in <code>document</code> */ public CContentAssistInvocationContext(final ITranslationUnit unit, boolean isCompletion) { super(); fTU= new Lazy<ITranslationUnit>() { @Override protected ITranslationUnit calculateValue() { return unit; } }; fEditor= null; fIsCompletion= isCompletion; fIsAutoActivated= false; } /** * Returns the translation unit that content assist is invoked in, <code>null</code> if there * is none. * * @return the translation unit that content assist is invoked in, possibly <code>null</code> */ @Override public ITranslationUnit getTranslationUnit() { assertNotDisposed(); return fTU.value(); } /** * Returns the project of the translation unit that content assist is invoked in, * <code>null</code> if none. * * @return the current C project, possibly <code>null</code> */ @Override public ICProject getProject() { assertNotDisposed(); ITranslationUnit unit= getTranslationUnit(); return unit == null ? null : unit.getCProject(); } @Override public IASTCompletionNode getCompletionNode() { assertNotDisposed(); // For scalability. if (fEditor != null && fEditor instanceof CEditor) { CEditor editor = (CEditor) fEditor; // Check to make sure we should attempt local parsing completions... for remote projects // we should not do this. if (!editor.shouldProcessLocalParsingCompletions()) { return null; } if (editor.isEnableScalablilityMode()) { if (editor.isParserBasedContentAssistDisabled()) { return null; } if (isAutoActivated() && editor.isContentAssistAutoActivartionDisabled()) { return null; } } } return fCN.value(); } @Override public int getParseOffset() { assertNotDisposed(); return fParseOffset.value(); } /** * @return the offset where context information (parameter hints) starts. */ @Override public int getContextInformationOffset() { assertNotDisposed(); return fContextInfoPosition.value(); } /** * Try to find a sensible completion position backwards in case the given offset * is inside a function call argument list or in template arguments. * * @param contextPosition the starting position * @return a sensible completion offset */ protected int guessCompletionPosition(int contextPosition) { assertNotDisposed(); CHeuristicScanner scanner= new CHeuristicScanner(getDocument()); int bound= Math.max(-1, contextPosition - 200); int pos= scanner.findNonWhitespaceBackward(contextPosition - 1, bound); if (pos == CHeuristicScanner.NOT_FOUND) return contextPosition; int token= scanner.previousToken(pos, bound); if (token == Symbols.TokenCOMMA) { int openingParenthesisPos = scanner.findOpeningPeer(pos, bound, '(', ')'); int openingAngleBracketPos = scanner.findOpeningPeer(pos, bound, '<', '>'); pos = Math.max(openingParenthesisPos, openingAngleBracketPos); if (pos == CHeuristicScanner.NOT_FOUND) return contextPosition; token = scanner.previousToken(pos, bound); } if (token == Symbols.TokenLPAREN || token == Symbols.TokenLESSTHAN) { pos= scanner.findNonWhitespaceBackward(pos - 1, bound); if (pos == CHeuristicScanner.NOT_FOUND) return contextPosition; token= scanner.previousToken(pos, bound); if (token == Symbols.TokenGREATERTHAN) { // skip template arguments pos= scanner.findOpeningPeer(pos - 1, '<', '>'); if (pos == CHeuristicScanner.NOT_FOUND) return contextPosition; pos= scanner.findNonWhitespaceBackward(pos - 1, bound); if (pos == CHeuristicScanner.NOT_FOUND) return contextPosition; token= scanner.previousToken(pos, bound); } if (token == Symbols.TokenIDENT) { return pos + 1; } } return contextPosition; } /** * Try to find the smallest offset inside the opening parenthesis of a function call * argument list. * * @return the offset of the function call parenthesis plus 1 or -1 if the invocation * offset is not inside a function call (or similar) */ protected int guessContextInformationPosition() { assertNotDisposed(); final int contextPosition= getInvocationOffset(); CHeuristicScanner scanner= new CHeuristicScanner(getDocument()); int bound= Math.max(-1, contextPosition - 200); // try the innermost scope of parentheses that looks like a method call int pos= contextPosition - 1; do { int paren= scanner.findOpeningPeer(pos, bound, '(', ')'); int angle= scanner.findOpeningPeer(pos, bound, '<', '>'); int nearestPeer = Math.max(paren, angle); if (nearestPeer == CHeuristicScanner.NOT_FOUND) break; int token= scanner.previousToken(nearestPeer - 1, bound); // next token must be a method name (identifier) or the closing angle of a // constructor call of a template type. if (token == Symbols.TokenIDENT || token == Symbols.TokenGREATERTHAN) { return nearestPeer + 1; } pos= nearestPeer - 1; } while (true); return -1; } /** * Get the editor content assist is invoked in. * * @return the editor, may be <code>null</code> */ @Override public IEditorPart getEditor() { assertNotDisposed(); return fEditor; } @Override public boolean isContextInformationStyle() { assertNotDisposed(); return !fIsCompletion || (getParseOffset() != getInvocationOffset()); } public boolean isAutoActivated() { assertNotDisposed(); return fIsAutoActivated; } @Override public void dispose() { if (fIndex != null) { fIndex.releaseReadLock(); fIndex = null; } super.dispose(); } public boolean isAfterOpeningParenthesis() { assertNotDisposed(); return afterOpeningParenthesis.value(); } public boolean isAfterOpeningAngleBracket() { assertNotDisposed(); return afterOpeningAngleBracket.value(); } public boolean isInUsingDirective() { assertNotDisposed(); return inUsingDeclaration.value(); } public boolean isFollowedBySemicolon() { assertNotDisposed(); return followedBySemicolon.value(); } public String getFunctionParameterDelimiter() { assertNotDisposed(); return functionParameterDelimiter.value(); } public String getTemplateParameterDelimiter() { assertNotDisposed(); return templateParameterDelimiter.value(); } }