From efe180944ffffb10a7830ba2994a172f52d9a094 Mon Sep 17 00:00:00 2001 From: Anton Leherbauer Date: Tue, 3 Apr 2007 12:31:00 +0000 Subject: [PATCH] Fix for 80831: Hover help should find declarations from included files and related bugs 80829, 80836, 158867 --- .../ui/text/c/hover/CSourceHover.java | 536 ++++++++++++++++-- 1 file changed, 497 insertions(+), 39 deletions(-) diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/c/hover/CSourceHover.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/c/hover/CSourceHover.java index 7249c29c0b5..f1302647af7 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/c/hover/CSourceHover.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/c/hover/CSourceHover.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2002, 2006 QNX Software Systems and others. + * Copyright (c) 2002, 2007 QNX Software Systems 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 @@ -7,25 +7,26 @@ * * Contributors: * QNX Software Systems - Initial API and implementation + * Anton Leherbauer (Wind River Systems) *******************************************************************************/ package org.eclipse.cdt.internal.ui.text.c.hover; import java.io.IOException; +import java.util.Arrays; import java.util.Set; -import org.eclipse.cdt.core.model.CModelException; -import org.eclipse.cdt.core.model.ICElement; -import org.eclipse.cdt.core.model.ISourceReference; -import org.eclipse.cdt.core.model.IWorkingCopy; -import org.eclipse.cdt.core.parser.KeywordSetKey; -import org.eclipse.cdt.core.parser.ParserFactory; -import org.eclipse.cdt.core.parser.ParserLanguage; -import org.eclipse.cdt.internal.ui.codemanipulation.StubUtility; -import org.eclipse.cdt.internal.ui.text.CCodeReader; -import org.eclipse.cdt.internal.ui.util.Strings; -import org.eclipse.cdt.ui.CUIPlugin; -import org.eclipse.cdt.ui.IWorkingCopyManager; +import org.eclipse.core.filebuffers.FileBuffers; +import org.eclipse.core.filebuffers.ITextFileBuffer; +import org.eclipse.core.filebuffers.ITextFileBufferManager; +import org.eclipse.core.filebuffers.LocationKind; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.Path; +import org.eclipse.core.runtime.Status; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.Document; import org.eclipse.jface.text.IDocument; @@ -34,17 +35,388 @@ import org.eclipse.jface.text.IInformationControlCreator; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextHoverExtension; import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.ITypedRegion; +import org.eclipse.jface.text.Region; +import org.eclipse.jface.text.TextUtilities; import org.eclipse.jface.text.information.IInformationProviderExtension2; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorPart; +import org.eclipse.cdt.core.dom.IName; +import org.eclipse.cdt.core.dom.ast.DOMException; +import org.eclipse.cdt.core.dom.ast.IASTFileLocation; +import org.eclipse.cdt.core.dom.ast.IASTFunctionStyleMacroParameter; +import org.eclipse.cdt.core.dom.ast.IASTName; +import org.eclipse.cdt.core.dom.ast.IASTPreprocessorFunctionStyleMacroDefinition; +import org.eclipse.cdt.core.dom.ast.IASTPreprocessorMacroDefinition; +import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit; +import org.eclipse.cdt.core.dom.ast.IBinding; +import org.eclipse.cdt.core.dom.ast.ICompositeType; +import org.eclipse.cdt.core.dom.ast.IEnumeration; +import org.eclipse.cdt.core.dom.ast.IEnumerator; +import org.eclipse.cdt.core.dom.ast.IFunction; +import org.eclipse.cdt.core.dom.ast.IMacroBinding; +import org.eclipse.cdt.core.dom.ast.IParameter; +import org.eclipse.cdt.core.dom.ast.IProblemBinding; +import org.eclipse.cdt.core.dom.ast.ITypedef; +import org.eclipse.cdt.core.dom.ast.IVariable; +import org.eclipse.cdt.core.dom.ast.cpp.ICPPTemplateDefinition; +import org.eclipse.cdt.core.index.IIndex; +import org.eclipse.cdt.core.index.IIndexName; +import org.eclipse.cdt.core.model.CModelException; +import org.eclipse.cdt.core.model.ICElement; +import org.eclipse.cdt.core.model.ISourceRange; +import org.eclipse.cdt.core.model.ISourceReference; +import org.eclipse.cdt.core.model.ITranslationUnit; +import org.eclipse.cdt.core.model.IWorkingCopy; +import org.eclipse.cdt.core.parser.KeywordSetKey; +import org.eclipse.cdt.core.parser.ParserFactory; +import org.eclipse.cdt.core.parser.ParserLanguage; +import org.eclipse.cdt.ui.CUIPlugin; +import org.eclipse.cdt.ui.IWorkingCopyManager; +import org.eclipse.cdt.ui.text.ICPartitions; + +import org.eclipse.cdt.internal.core.model.ASTCache.ASTRunnable; +import org.eclipse.cdt.internal.core.model.ext.ICElementHandle; + +import org.eclipse.cdt.internal.ui.editor.ASTProvider; +import org.eclipse.cdt.internal.ui.text.CCodeReader; +import org.eclipse.cdt.internal.ui.text.CHeuristicScanner; +import org.eclipse.cdt.internal.ui.util.Strings; +import org.eclipse.cdt.internal.ui.viewsupport.CElementLabels; +import org.eclipse.cdt.internal.ui.viewsupport.IndexUI; + /** - * CSourceHover + * A text hover presenting the source of the element under the cursor. */ public class CSourceHover extends AbstractCEditorTextHover implements ITextHoverExtension, IInformationProviderExtension2 { + private static final boolean DEBUG = false; + + /** + * Computes the source location for a given identifier. + */ + private static class ComputeSourceRunnable implements ASTRunnable { + + private final ITranslationUnit fTU; + private final IRegion fTextRegion; + private final IProgressMonitor fMonitor; + private String fSource; + + /** + * @param tUnit + * @param textRegion + */ + public ComputeSourceRunnable(ITranslationUnit tUnit, IRegion textRegion) { + fTU= tUnit; + fTextRegion= textRegion; + fMonitor= new NullProgressMonitor(); + fSource= null; + } + + /* + * @see org.eclipse.cdt.internal.core.model.ASTCache.ASTRunnable#runOnAST(org.eclipse.cdt.core.dom.ast.IASTTranslationUnit) + */ + public IStatus runOnAST(IASTTranslationUnit ast) { + if (ast != null) { + try { + IASTName[] names; + names = fTU.getLanguage().getSelectedNames(ast, fTextRegion.getOffset(), fTextRegion.getLength()); + if (names != null && names.length == 1) { + IBinding binding= names[0].resolveBinding(); + if (binding != null) { + if (binding instanceof IProblemBinding) { + if (DEBUG) fSource= "Cannot resolve " + new String(names[0].toCharArray()); //$NON-NLS-1$ + } else if (binding instanceof IMacroBinding) { + fSource= computeSourceForMacro(ast, names[0], binding); + } else { + fSource= computeSourceForBinding(ast, binding); + } + if (fSource != null) { + return Status.OK_STATUS; + } + } + } + } catch (CoreException exc) { + return exc.getStatus(); + } catch (DOMException exc) { + return new Status(IStatus.ERROR, CUIPlugin.PLUGIN_ID, "Internal Error", exc); //$NON-NLS-1$ + } + } + return Status.CANCEL_STATUS; + } + + /** + * Compute the source for a macro. If the source location of the macro definition can be determined, + * the source is taken from there, otherwise the source is constructed as a #define directive. + * + * @param ast the AST of the translation unit + * @param name the macro occurrence in the AST + * @param binding the binding of the macro name + * @return the source or null + * @throws CoreException + */ + private String computeSourceForMacro(IASTTranslationUnit ast, IASTName name, IBinding binding) throws CoreException { + IASTPreprocessorMacroDefinition macroDef= null; + final char[] macroName= name.toCharArray(); + + // search for macro definition, there should be a more efficient way + IASTPreprocessorMacroDefinition[] macroDefs; + final IASTPreprocessorMacroDefinition[] localMacroDefs= ast.getMacroDefinitions(); + for (macroDefs= localMacroDefs; macroDefs != null; macroDefs= (macroDefs == localMacroDefs) ? ast.getBuiltinMacroDefinitions() : null) { + for (int i = 0; i < macroDefs.length; i++) { + if (Arrays.equals(macroDefs[i].getName().toCharArray(), macroName)) { + macroDef= macroDefs[i]; + break; + } + } + } + if (macroDef != null) { + String source= computeSourceForName(macroDef.getName(), binding); + if (source != null) { + return source; + } + IASTFunctionStyleMacroParameter[] parameters= {}; + if (macroDef instanceof IASTPreprocessorFunctionStyleMacroDefinition) { + parameters= ((IASTPreprocessorFunctionStyleMacroDefinition)macroDef).getParameters(); + } + StringBuffer buf= new StringBuffer(macroName.length + macroDef.getExpansion().length() + parameters.length*5 + 10); + buf.append("#define ").append(macroName); //$NON-NLS-1$ + if (parameters.length > 0) { + buf.append('('); + for (int i = 0; i < parameters.length; i++) { + if (i > 0) { + buf.append(", "); //$NON-NLS-1$ + } + IASTFunctionStyleMacroParameter parameter = parameters[i]; + buf.append(parameter.getParameter()); + } + buf.append(')'); + } + String expansion= macroDef.getExpansion(); + if (expansion != null) { + buf.append(' ').append(expansion); + } + return buf.toString(); + } + return null; + } + + /** + * Find a definition or declaration for the given binding and returns the source for it. + * Definitions are preferred over declarations. In case of multiple definitions or declarations, + * and the first name which yields source is taken. + * + * @param ast the AST of the translation unit + * @param binding the binding + * @return a source string or null, if no source could be computed + * @throws CoreException if the source file could not be loaded or if there was a + * problem with the index + * @throws DOMException if there was an internal problem with the DOM + */ + private String computeSourceForBinding(IASTTranslationUnit ast, IBinding binding) throws CoreException, DOMException { + IName[] names= findDefinitions(ast, binding); + if (names.length == 0) { + names= findDeclarations(ast, binding); + } + if (names.length > 0) { + for (int i = 0; i < names.length; i++) { + String source= computeSourceForName(names[0], binding); + if (source != null) { + return source; + } + } + // fallback: return signature only + ICElementHandle cElement= null; + if (names[0] instanceof IIndexName) { + cElement= IndexUI.getCElementForName(fTU.getCProject(), ast.getIndex(), (IIndexName)names[0]); + } else if (names[0] instanceof IASTName) { + cElement= IndexUI.getCElementForName(fTU.getCProject(), ast.getIndex(), (IASTName)names[0]); + } + if (cElement != null) { + return CElementLabels.getTextLabel(cElement, CElementLabels.DEFAULT_QUALIFIED); + } + } + return null; + } + + /** + * Get the source for the given name from the underlying file. + * + * @param name the name to get the source for + * @param binding the binding of the name + * @return the source string or null, if the source could not be computed + * @throws CoreException if the file could not be loaded + */ + private String computeSourceForName(IName name, IBinding binding) throws CoreException { + IASTFileLocation fileLocation= name.getFileLocation(); + if (fileLocation == null) { + return null; + } + String fileName= fileLocation.getFileName(); + if (DEBUG) System.out.println("[CSourceHover] Computing source for " + new String(name.toCharArray()) + " in " + fileName); //$NON-NLS-1$//$NON-NLS-2$ + IPath location= Path.fromOSString(fileName); + LocationKind locationKind= LocationKind.LOCATION; + if (name instanceof IASTName) { + IASTName astName= (IASTName)name; + if (astName.getContainingFilename().equals(fileName)) { + // reuse editor buffer for names local to the translation unit + location= fTU.getPath(); + locationKind= LocationKind.IFILE; + } + } + ITextFileBufferManager mgr= FileBuffers.getTextFileBufferManager(); + mgr.connect(location, locationKind, fMonitor); + ITextFileBuffer buffer= mgr.getTextFileBuffer(location, locationKind); + String source; + try { + IRegion nameRegion= new Region(fileLocation.getNodeOffset(), fileLocation.getNodeLength()); + final int nameOffset= nameRegion.getOffset(); + int sourceStart= nameOffset; + int sourceEnd= nameOffset + nameRegion.getLength(); + IDocument doc= buffer.getDocument(); + // expand source range to include preceding comment, if any + CHeuristicScanner scanner= new CHeuristicScanner(doc); + int commentBound; + if (binding instanceof IParameter) { + sourceStart= scanner.scanBackward(nameOffset, CHeuristicScanner.UNBOUND, new char[] { '(', ',' }); + if (sourceStart == CHeuristicScanner.NOT_FOUND) { + return null; + } + sourceStart= scanner.findNonWhitespaceForward(sourceStart + 1, nameOffset); + } else if (binding instanceof IEnumerator) { + sourceStart= scanner.scanBackward(nameOffset, CHeuristicScanner.UNBOUND, new char[] { '{', ',' }); + if (sourceStart == CHeuristicScanner.NOT_FOUND) { + return null; + } + sourceStart= scanner.findNonWhitespaceForward(sourceStart + 1, nameOffset); + } else { + final int nameLine= doc.getLineOfOffset(nameOffset); + sourceStart= doc.getLineOffset(nameLine); + commentBound= scanner.scanBackward(sourceStart, CHeuristicScanner.UNBOUND, new char[] { '{', '}', ';' }); + if (commentBound == CHeuristicScanner.NOT_FOUND) { + commentBound= -1; + } + int commentStart= searchCommentBackward(doc, sourceStart, commentBound); + if (commentStart >= 0) { + sourceStart= commentStart; + } + } + // expand forward to the end of the definition/declaration + if (binding instanceof IMacroBinding) { + ITypedRegion partition= TextUtilities.getPartition(doc, ICPartitions.C_PARTITIONING, nameOffset, false); + if (ICPartitions.C_PREPROCESSOR.equals(partition.getType())) { + sourceStart= partition.getOffset(); + sourceEnd= sourceStart + partition.getLength(); + } + } else { + boolean searchBrace= false; + boolean searchSemi= false; + boolean searchComma= false; + if (!name.isDefinition()) { + searchSemi= true; + } else if (binding instanceof ICompositeType || binding instanceof IEnumeration || binding instanceof IFunction) { + searchBrace= true; + } else if (binding instanceof ICPPTemplateDefinition) { + searchBrace= true; + } else if (binding instanceof IParameter || binding instanceof IEnumerator) { + searchComma= true; + } else if (binding instanceof IVariable || binding instanceof ITypedef) { + searchSemi= true; + } + if (searchBrace) { + int brace= scanner.scanForward(nameOffset, CHeuristicScanner.UNBOUND, '{'); + if (brace != CHeuristicScanner.NOT_FOUND) { + sourceEnd= scanner.findClosingPeer(brace + 1, '{', '}'); + if (sourceEnd == CHeuristicScanner.NOT_FOUND) { + sourceEnd= doc.getLength(); + } + } + // expand region to include whole line + IRegion lineRegion= doc.getLineInformationOfOffset(sourceEnd); + sourceEnd= lineRegion.getOffset() + lineRegion.getLength(); + } else if (searchSemi) { + int semi= scanner.scanForward(nameOffset, CHeuristicScanner.UNBOUND, ';'); + if (semi != CHeuristicScanner.NOT_FOUND) { + sourceEnd= semi+1; + } + // expand region to include whole line + IRegion lineRegion= doc.getLineInformationOfOffset(sourceEnd); + sourceEnd= lineRegion.getOffset() + lineRegion.getLength(); + } else if (searchComma) { + int bound= Math.min(doc.getLength(), nameOffset + 100); + int comma= scanner.scanForward(nameOffset, bound, ','); + if (comma != CHeuristicScanner.NOT_FOUND) { + sourceEnd= comma; + } + // expand region to include whole line if rest is white space or comment + int nextNonWS= scanner.findNonWhitespaceForward(sourceEnd + 1, bound); + if (doc.getLineOfOffset(nextNonWS) > doc.getLineOfOffset(sourceEnd)) { + IRegion lineRegion= doc.getLineInformationOfOffset(sourceEnd); + sourceEnd= lineRegion.getOffset() + lineRegion.getLength(); + } + } + } + + source= buffer.getDocument().get(sourceStart, sourceEnd - sourceStart); + return source; + + } catch (BadLocationException exc) { + // ignore - should not happen anyway + if (DEBUG) exc.printStackTrace(); + } finally { + mgr.disconnect(location, LocationKind.LOCATION, fMonitor); + } + return null; + } + + /** + * Search for definitions for the given binding. + * + * @param ast the AST of the translation unit + * @param binding the binding + * @return an array of definitions, never null + * @throws CoreException + */ + private IName[] findDefinitions(IASTTranslationUnit ast, + IBinding binding) throws CoreException { + IName[] declNames= ast.getDefinitionsInAST(binding); + if (declNames.length == 0 && ast.getIndex() != null) { + // search definitions in index + declNames = ast.getIndex().findDefinitions(binding); + } + return declNames; + } + + /** + * Search for declarations for the given binding. + * + * @param ast the AST of the translation unit + * @param binding the binding + * @return an array of declarations, never null + * @throws CoreException + */ + private IName[] findDeclarations(IASTTranslationUnit ast, + IBinding binding) throws CoreException { + IName[] declNames= ast.getDeclarationsInAST(binding); + if (declNames.length == 0 && ast.getIndex() != null) { + // search declarations in index + declNames= ast.getIndex().findNames(binding, IIndex.FIND_DECLARATIONS); + } + return declNames; + } + + /** + * @return the computed source or null, if no source could be computed + */ + public String getSource() { + return fSource; + } + + } + /** * */ @@ -72,26 +444,22 @@ public class CSourceHover extends AbstractCEditorTextHover implements ITextHover if (expression.length() == 0) return null; - String source = null; + String source= null; ICElement curr = copy.getElement(expression); if (curr == null) { // Try with the indexer - source = findMatches(expression, textViewer); + source= searchInIndex(expression, hoverRegion); } else { - source= ((ISourceReference) curr).getSource(); + source= getSourceForCElement(textViewer.getDocument(), curr); } if (source == null || source.trim().length() == 0) return null; - source= removeLeadingComments(source); - String delim= null; + // we are actually interested in the comments, too. +// source= removeLeadingComments(source); - try { - delim= StubUtility.getLineDelimiterUsed(curr); - } catch (CModelException e) { - delim= System.getProperty("line.separator", "\n"); //$NON-NLS-1$ //$NON-NLS-2$ - } + String delim= System.getProperty("line.separator", "\n"); //$NON-NLS-1$ //$NON-NLS-2$ String[] sourceLines= Strings.convertIntoLines(source); String firstLine= sourceLines[0]; @@ -112,12 +480,97 @@ public class CSourceHover extends AbstractCEditorTextHover implements ITextHover return null; } + /** + * Return the source for the given element including the preceding comment. + * + * @param document the document of the current editor + * @param cElement the element to compute the source from + * @return the source or null + * @throws CModelException + * @throws BadLocationException + */ + private String getSourceForCElement(IDocument doc, ICElement cElement) throws CModelException, BadLocationException { + if (!(cElement instanceof ISourceReference)) { + return null; + } + ISourceRange sourceRange= ((ISourceReference)cElement).getSourceRange(); + int sourceStart= sourceRange.getStartPos(); + int sourceEnd= sourceStart + sourceRange.getLength(); + CHeuristicScanner scanner= new CHeuristicScanner(doc); + int commentBound= scanner.scanBackward(sourceStart, CHeuristicScanner.UNBOUND, new char[] { '{', '}', ';' }); + if (commentBound == CHeuristicScanner.NOT_FOUND) { + commentBound= -1; + } + int commentStart= searchCommentBackward(doc, sourceStart, commentBound); + if (commentStart >= 0) { + sourceStart= commentStart; + } + return doc.get(sourceStart, sourceEnd - sourceStart + 1); + } + + /** + * Searches the start of the comment preceding the given source offset. + * Continuous line comments are considered as one comment until a block + * comment is reached or a non-comment partition. + * + * @param doc the document + * @param start the start of the backward search + * @param bound search boundary (exclusive) + * @return the comment start offset or -1, if no suitable comment was found + * @throws BadLocationException + */ + private static int searchCommentBackward(IDocument doc, int start, int bound) throws BadLocationException { + int firstLine= doc.getLineOfOffset(start); + if (firstLine == 0) { + return -1; + } + int currentOffset= doc.getLineOffset(firstLine - 1); + int commentOffset= -1; + while (currentOffset > bound) { + ITypedRegion partition= TextUtilities.getPartition(doc, ICPartitions.C_PARTITIONING, currentOffset, false); + currentOffset= partition.getOffset() - 1; + if (ICPartitions.C_MULTI_LINE_COMMENT.equals(partition.getType())) { + int previousCommentOffset= commentOffset; + commentOffset = partition.getOffset(); + final int startLine= doc.getLineOfOffset(commentOffset); + final int lineOffset= doc.getLineOffset(startLine); + if (commentOffset == lineOffset || + doc.get(lineOffset, commentOffset - lineOffset).trim().length() == 0) { + return lineOffset; + } + return previousCommentOffset; + } else if (ICPartitions.C_SINGLE_LINE_COMMENT.equals(partition.getType())) { + commentOffset = partition.getOffset(); + final int startLine= doc.getLineOfOffset(commentOffset); + final int lineOffset= doc.getLineOffset(startLine); + if (commentOffset == lineOffset || + doc.get(lineOffset, commentOffset - lineOffset).trim().length() == 0) { + continue; + } + return commentOffset; + } else if (IDocument.DEFAULT_CONTENT_TYPE.equals(partition.getType())) { + if (commentOffset >= 0) { + break; + } + } else { + break; + } + } + return commentOffset; + } + private static int getTabWidth() { return 4; } - private String removeLeadingComments(String source) { + /** + * Strip the leading comment from the given source string. + * + * @param source + * @return the source string without leading comments + */ + protected static String removeLeadingComments(String source) { CCodeReader reader= new CCodeReader(); IDocument document= new Document(source); int i; @@ -145,33 +598,38 @@ public class CSourceHover extends AbstractCEditorTextHover implements ITextHover return source.substring(i); } - private String findMatches(String name, ITextViewer textViewer) { + private String searchInIndex(String name, IRegion textRegion) { + //Before trying a search lets make sure that the user is not hovering over a keyword + if (selectionIsKeyword(name)) + return null; + IEditorPart editor = getEditor(); if (editor != null) { IEditorInput input= editor.getEditorInput(); IWorkingCopyManager manager= CUIPlugin.getDefault().getWorkingCopyManager(); - IWorkingCopy copy = manager.getWorkingCopy(input); - - //Before trying a search lets make sure that the user is not hovering over a keyword (which will - //result in null being returned) - if (selectionIsKeyword(name)) - return null; + IWorkingCopy copy= manager.getWorkingCopy(input); if (copy != null) { - // TODO actually find some matches... + IProgressMonitor monitor= new NullProgressMonitor(); + ComputeSourceRunnable computer= new ComputeSourceRunnable(copy, textRegion); + ASTProvider.getASTProvider().runOnAST(copy, ASTProvider.WAIT_ACTIVE_ONLY, monitor, computer); + return computer.getSource(); } } return null; } + /** + * Test whether the given name is a known keyword. + * + * @param name + * @return true if the name is a known keyword or false if the + * name is not considered a keyword + */ private boolean selectionIsKeyword(String name) { - - Set keywords =ParserFactory.getKeywordSet(KeywordSetKey.KEYWORDS, ParserLanguage.CPP ); - if (keywords.contains(name)) - return true; - - return false; + Set keywords= ParserFactory.getKeywordSet(KeywordSetKey.KEYWORDS, ParserLanguage.CPP); + return keywords.contains(name); } /*