blob: 53d78cfa6f5ebf4f9f2f92c02695c5a41bebf9e9 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2002, 2016 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
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* QNX Software Systems - Initial API and implementation
* Anton Leherbauer (Wind River Systems)
* Sergey Prigogin (Google)
* Paulo Garcia (BlackBerry)
*******************************************************************************/
package org.eclipse.cdt.internal.ui.text.c.hover;
import java.io.IOException;
import java.util.Set;
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.resources.IFile;
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.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IInformationControl;
import org.eclipse.jface.text.IInformationControlCreator;
import org.eclipse.jface.text.IRegion;
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.swt.SWT;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.part.IWorkbenchPartOrientation;
import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.IPositionConverter;
import org.eclipse.cdt.core.dom.IName;
import org.eclipse.cdt.core.dom.ast.ASTTypeUtil;
import org.eclipse.cdt.core.dom.ast.DOMException;
import org.eclipse.cdt.core.dom.ast.IASTDeclSpecifier;
import org.eclipse.cdt.core.dom.ast.IASTDeclaration;
import org.eclipse.cdt.core.dom.ast.IASTDeclarator;
import org.eclipse.cdt.core.dom.ast.IASTFileLocation;
import org.eclipse.cdt.core.dom.ast.IASTFunctionDefinition;
import org.eclipse.cdt.core.dom.ast.IASTImplicitNameOwner;
import org.eclipse.cdt.core.dom.ast.IASTName;
import org.eclipse.cdt.core.dom.ast.IASTNode;
import org.eclipse.cdt.core.dom.ast.IASTNodeSelector;
import org.eclipse.cdt.core.dom.ast.IASTParameterDeclaration;
import org.eclipse.cdt.core.dom.ast.IASTSimpleDeclSpecifier;
import org.eclipse.cdt.core.dom.ast.IASTSimpleDeclaration;
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.IProblemType;
import org.eclipse.cdt.core.dom.ast.IType;
import org.eclipse.cdt.core.dom.ast.ITypedef;
import org.eclipse.cdt.core.dom.ast.IVariable;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTDeclSpecifier;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTDeclarator;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTSimpleDeclSpecifier;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTTemplateId;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTTypeId;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPAliasTemplateInstance;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPConstructor;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPSpecialization;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPTemplateDefinition;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPTemplateParameter;
import org.eclipse.cdt.core.dom.ast.gnu.c.ICASTKnRFunctionDeclarator;
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.ILanguage;
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.Keywords;
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.dom.parser.ASTQueries;
import org.eclipse.cdt.internal.core.dom.parser.cpp.ICPPUnknownBinding;
import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.CPPVisitor;
import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.HeuristicResolver;
import org.eclipse.cdt.internal.core.model.ASTCache.ASTRunnable;
import org.eclipse.cdt.internal.corext.util.Strings;
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.EditorUtility;
/**
* A text hover presenting the source of the element under the cursor.
*/
public class CSourceHover extends AbstractCEditorTextHover {
private static final boolean DEBUG = false;
protected static class SingletonRule implements ISchedulingRule {
public static final ISchedulingRule INSTANCE = new SingletonRule();
@Override
public boolean contains(ISchedulingRule rule) {
return rule == this;
}
@Override
public boolean isConflicting(ISchedulingRule rule) {
return rule == this;
}
}
/**
* Computes the source location for a given identifier.
*/
protected static class ComputeSourceRunnable implements ASTRunnable {
private final ITranslationUnit fTU;
private final IRegion fTextRegion;
private final String fSelection;
private final IProgressMonitor fMonitor;
private String fSource;
/**
* @param tUnit the translation unit
* @param textRegion the selected region
* @param selection the text of the selected region without
*/
public ComputeSourceRunnable(ITranslationUnit tUnit, IRegion textRegion, String selection) {
fTU= tUnit;
fTextRegion= textRegion;
fSelection = selection;
fMonitor= new NullProgressMonitor();
fSource= null;
}
@Override
public IStatus runOnAST(ILanguage lang, IASTTranslationUnit ast) {
if (ast != null) {
try {
IASTNodeSelector nodeSelector = ast.getNodeSelector(null);
if (fSelection.equals(Keywords.AUTO)) {
IASTNode node = nodeSelector.findEnclosingNode(fTextRegion.getOffset(), fTextRegion.getLength());
if (node instanceof ICPPASTDeclSpecifier) {
ICPPASTDeclSpecifier declSpec = (ICPPASTDeclSpecifier) node;
IASTNode parent = declSpec.getParent();
IASTDeclarator[] declarators = IASTDeclarator.EMPTY_DECLARATOR_ARRAY;
if (parent instanceof IASTSimpleDeclaration) {
declarators = ((IASTSimpleDeclaration) parent).getDeclarators();
} else if (parent instanceof IASTParameterDeclaration) {
declarators = new IASTDeclarator[] { ((IASTParameterDeclaration) parent).getDeclarator() };
} else if (parent instanceof ICPPASTTypeId) {
declarators = new IASTDeclarator[] { ((ICPPASTTypeId) parent).getAbstractDeclarator() };
}
IType type = null;
for (IASTDeclarator declarator : declarators) {
IType t = CPPVisitor.createType(declarator);
if (type == null) {
type = t;
} else if (!type.isSameType(t)) {
// Type varies between declarators - don't display anything.
type = null;
break;
}
}
if (type != null && !(type instanceof IProblemType))
fSource = ASTTypeUtil.getType(type, false);
}
} else {
IASTName name= nodeSelector.findEnclosingName(fTextRegion.getOffset(), fTextRegion.getLength());
if (name != null) {
IASTNode parent = name.getParent();
if (parent instanceof ICPPASTTemplateId) {
name = (IASTName) parent;
}
IBinding binding= name.resolveBinding();
if (binding != null) {
// Check for implicit names first, could be an implicit constructor call.
if (name.getParent() instanceof IASTImplicitNameOwner) {
IASTImplicitNameOwner implicitNameOwner = (IASTImplicitNameOwner) name.getParent();
IASTName[] implicitNames = implicitNameOwner.getImplicitNames();
if (implicitNames.length == 1) {
IBinding implicitNameBinding = implicitNames[0].resolveBinding();
if (implicitNameBinding instanceof ICPPConstructor) {
binding = implicitNameBinding;
}
}
}
if (binding instanceof ICPPUnknownBinding) {
IBinding[] resolved = HeuristicResolver
.resolveUnknownBinding((ICPPUnknownBinding) binding, name);
if (resolved.length == 1) {
binding = resolved[0];
}
}
if (binding instanceof IProblemBinding) {
// Report problem as source comment.
if (DEBUG) {
IProblemBinding problem= (IProblemBinding) binding;
fSource= "/* Problem:\n" + //$NON-NLS-1$
" * " + problem.getMessage() + //$NON-NLS-1$
"\n */"; //$NON-NLS-1$
}
} else if (binding instanceof IMacroBinding) {
fSource= computeSourceForMacro(ast, name, binding);
} else if (binding instanceof IEnumerator) {
// Show integer value for enumerators (bug 285126).
fSource= computeSourceForEnumerator(ast, (IEnumerator) binding);
} else {
fSource= computeSourceForBinding(ast, binding);
}
}
}
}
if (fSource != null) {
return Status.OK_STATUS;
}
} catch (CoreException e) {
return e.getStatus();
} catch (DOMException e) {
return new Status(IStatus.ERROR, CUIPlugin.PLUGIN_ID, "Internal Error", e); //$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 {@code #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 {@code null}
* @throws CoreException
*/
private String computeSourceForMacro(IASTTranslationUnit ast, IASTName name, IBinding binding)
throws CoreException {
// Search for the macro definition
IName[] defs = ast.getDefinitions(binding);
for (IName def : defs) {
String source= computeSourceForName(def, binding);
if (source != null) {
return source;
}
}
return null;
}
/**
* Computes the source for a enumerator. If the value of the enumerator can be retrieved,
* the method will return a string with the value, otherwise it will fall back showing
* the enumerator constant.
*
* @param ast the AST of the translation unit
* @param binding the binding of the enumerator name
* @return the enumerator value, source or {@code null}
* @throws CoreException
*/
private String computeSourceForEnumerator(IASTTranslationUnit ast, IEnumerator binding)
throws CoreException {
Number numValue = binding.getValue().numberValue();
if (numValue != null) {
return numValue.toString();
} else {
// Search for the enumerator definition
IName[] defs = ast.getDefinitions(binding);
for (IName def : defs) {
String source= computeSourceForName(def, binding);
if (source != null) {
return source;
}
}
}
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 {@code 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 = findDefsOrDecls(ast, binding);
// In case the binding is a non-explicit specialization we need
// to consider the original binding (bug 281396).
while (names.length == 0 && binding instanceof ICPPSpecialization) {
IBinding specializedBinding = ((ICPPSpecialization) binding).getSpecializedBinding();
if (specializedBinding == null || specializedBinding instanceof IProblemBinding) {
break;
}
names = findDefsOrDecls(ast, specializedBinding);
binding = specializedBinding;
}
if (names.length > 0) {
for (IName name : names) {
String source= computeSourceForName(name, binding);
if (source != null) {
return source;
}
}
}
return null;
}
private IName[] findDefsOrDecls(IASTTranslationUnit ast, IBinding binding) throws CoreException {
IName[] names= findDefinitions(ast, binding);
if (names.length == 0) {
names= findDeclarations(ast, binding);
}
return names;
}
/**
* Returns 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 {@code 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;
}
int nodeOffset= fileLocation.getNodeOffset();
int nodeLength= fileLocation.getNodeLength();
String fileName= fileLocation.getFileName();
if (DEBUG) System.out.println("[CSourceHover] Computing source for " + name + " in " + fileName); //$NON-NLS-1$//$NON-NLS-2$
IPath location= Path.fromOSString(fileName);
LocationKind locationKind= LocationKind.LOCATION;
if (name instanceof IASTName && !name.isReference()) {
IASTName astName= (IASTName) name;
if (astName.getTranslationUnit().getFilePath().equals(fileName)) {
int hoverOffset = fTextRegion.getOffset();
if (hoverOffset <= nodeOffset && nodeOffset < hoverOffset + fTextRegion.getLength() ||
hoverOffset >= nodeOffset && hoverOffset < nodeOffset + nodeLength) {
// Bug 359352 - don't show source if its the same we are hovering on.
return computeHoverForDeclaration(astName);
}
if (fTU.getResource() != null) {
// Reuse editor buffer for names local to the translation unit
location= fTU.getResource().getFullPath();
locationKind= LocationKind.IFILE;
}
}
} else {
// Try to resolve path to a resource for proper encoding (bug 221029)
IFile file= EditorUtility.getWorkspaceFileAtLocation(location, fTU);
if (file != null) {
location= file.getFullPath();
locationKind= LocationKind.IFILE;
if (name instanceof IIndexName) {
// Need to adjust index offsets to current offsets
// in case file has been modified since last index time.
IIndexName indexName= (IIndexName) name;
long timestamp= indexName.getFile().getTimestamp();
IPositionConverter converter= CCorePlugin.getPositionTrackerManager().findPositionConverter(file, timestamp);
if (converter != null) {
IRegion currentLocation= converter.historicToActual(new Region(nodeOffset, nodeLength));
nodeOffset= currentLocation.getOffset();
nodeLength= currentLocation.getLength();
}
}
}
}
ITextFileBufferManager mgr= FileBuffers.getTextFileBufferManager();
mgr.connect(location, locationKind, fMonitor);
ITextFileBuffer buffer= mgr.getTextFileBuffer(location, locationKind);
try {
IRegion nameRegion= new Region(nodeOffset, nodeLength);
final int nameOffset= nameRegion.getOffset();
final int sourceStart;
final int sourceEnd;
IDocument doc= buffer.getDocument();
if (nameOffset >= doc.getLength() || nodeLength <= 0) {
return null;
}
if (binding instanceof IMacroBinding) {
ITypedRegion partition= TextUtilities.getPartition(doc, ICPartitions.C_PARTITIONING, nameOffset, false);
if (ICPartitions.C_PREPROCESSOR.equals(partition.getType())) {
int directiveStart= partition.getOffset();
int commentStart= searchCommentBackward(doc, directiveStart, -1);
if (commentStart >= 0) {
sourceStart= commentStart;
} else {
sourceStart= directiveStart;
}
sourceEnd= directiveStart + partition.getLength();
} else {
return null;
}
} else {
// Expand source range to include preceding comment, if any
boolean isKnR= isKnRSource(name);
sourceStart= computeSourceStart(doc, nameOffset, binding, isKnR);
if (sourceStart == CHeuristicScanner.NOT_FOUND) {
return null;
}
sourceEnd= computeSourceEnd(doc, nameOffset + nameRegion.getLength(), binding, name.isDefinition(), isKnR);
}
String source= buffer.getDocument().get(sourceStart, sourceEnd - sourceStart);
return source;
} catch (BadLocationException e) {
CUIPlugin.log(e);
} finally {
mgr.disconnect(location, LocationKind.LOCATION, fMonitor);
}
return null;
}
/**
* Computes the hover containing the deduced type for a declaration based on {@code auto}
* keyword.
*
* @param name the name of the declarator
* @return the hover text, if the declaration is based on {@code auto} keyword,
* otherwise {@code null}.
*/
private String computeHoverForDeclaration(IASTName name) {
ICPPASTDeclarator declarator = ASTQueries.findAncestorWithType(name, ICPPASTDeclarator.class);
if (declarator == null)
return null;
IASTDeclaration declaration = ASTQueries.findAncestorWithType(declarator, IASTDeclaration.class);
IASTDeclSpecifier declSpec = null;
if (declaration instanceof IASTSimpleDeclaration) {
declSpec = ((IASTSimpleDeclaration) declaration).getDeclSpecifier();
} else if (declaration instanceof IASTParameterDeclaration) {
declSpec = ((IASTParameterDeclaration) declaration).getDeclSpecifier();
}
if (!(declSpec instanceof ICPPASTSimpleDeclSpecifier) ||
((ICPPASTSimpleDeclSpecifier) declSpec).getType() != IASTSimpleDeclSpecifier.t_auto) {
return null;
}
IType type = CPPVisitor.createType(declarator);
if (type instanceof IProblemType)
return null;
return ASTTypeUtil.getType(type, false) + " " + name.getRawSignature(); //$NON-NLS-1$
}
/**
* Determines if the name is part of a KnR function definition.
*
* @param name
* @return {@code true} if the name is part of a KnR function
*/
private boolean isKnRSource(IName name) {
if (name instanceof IASTName) {
IASTNode node= (IASTNode)name;
while (node.getParent() != null) {
if (node instanceof ICASTKnRFunctionDeclarator) {
return node.getParent() instanceof IASTFunctionDefinition;
}
node= node.getParent();
}
}
return false;
}
private int computeSourceStart(IDocument doc, int nameOffset, IBinding binding, boolean isKnR) throws BadLocationException {
int sourceStart= nameOffset;
CHeuristicScanner scanner= new CHeuristicScanner(doc);
if (binding instanceof IParameter) {
if (isKnR) {
sourceStart= scanner.scanBackward(nameOffset, CHeuristicScanner.UNBOUND, new char[] { ')', ';' });
} else {
sourceStart= scanner.scanBackward(nameOffset, CHeuristicScanner.UNBOUND, new char[] { '>', '(', ',' });
if (sourceStart > 0 && doc.getChar(sourceStart) == '>') {
sourceStart= scanner.findOpeningPeer(sourceStart - 1, '<', '>');
if (sourceStart > 0) {
sourceStart= scanner.scanBackward(sourceStart, CHeuristicScanner.UNBOUND, new char[] { '(', ',' });
}
}
}
if (sourceStart == CHeuristicScanner.NOT_FOUND) {
return sourceStart;
}
sourceStart= scanner.findNonWhitespaceForward(sourceStart + 1, nameOffset);
if (sourceStart == CHeuristicScanner.NOT_FOUND) {
sourceStart = nameOffset;
}
} else if (binding instanceof ICPPTemplateParameter) {
sourceStart= scanner.scanBackward(nameOffset, CHeuristicScanner.UNBOUND, new char[] { '>', '<', ',' });
if (sourceStart > 0 && doc.getChar(sourceStart) == '>') {
sourceStart= scanner.findOpeningPeer(sourceStart - 1, '<', '>');
if (sourceStart > 0) {
sourceStart= scanner.scanBackward(sourceStart, CHeuristicScanner.UNBOUND, new char[] { '<', ',' });
}
}
if (sourceStart == CHeuristicScanner.NOT_FOUND) {
return sourceStart;
}
sourceStart= scanner.findNonWhitespaceForward(sourceStart + 1, nameOffset);
if (sourceStart == CHeuristicScanner.NOT_FOUND) {
sourceStart = nameOffset;
}
} else if (binding instanceof IEnumerator) {
sourceStart= scanner.scanBackward(nameOffset, CHeuristicScanner.UNBOUND, new char[] { '{', ',' });
if (sourceStart == CHeuristicScanner.NOT_FOUND) {
return sourceStart;
}
sourceStart= scanner.findNonWhitespaceForward(sourceStart + 1, nameOffset);
if (sourceStart == CHeuristicScanner.NOT_FOUND) {
sourceStart = nameOffset;
}
} else {
final boolean expectClosingBrace;
IType type= null;
if (binding instanceof ITypedef) {
type= ((ITypedef)binding).getType();
} else if (binding instanceof IVariable) {
type= ((IVariable)binding).getType();
}
expectClosingBrace= (type instanceof ICompositeType || type instanceof IEnumeration) && !(binding instanceof IVariable);
final int nameLine= doc.getLineOfOffset(nameOffset);
sourceStart= nameOffset;
int commentBound;
if (isKnR) {
commentBound= scanner.scanBackward(sourceStart, CHeuristicScanner.UNBOUND, new char[] { ')', ';' });
} else {
commentBound= scanner.scanBackward(sourceStart, CHeuristicScanner.UNBOUND, new char[] { '{', '}', ';' });
}
while (expectClosingBrace && commentBound > 0 && doc.getChar(commentBound) == '}') {
int openingBrace= scanner.findOpeningPeer(commentBound - 1, '{', '}');
if (openingBrace != CHeuristicScanner.NOT_FOUND) {
sourceStart= openingBrace - 1;
}
if (isKnR) {
commentBound= scanner.scanBackward(sourceStart, CHeuristicScanner.UNBOUND, new char[] { ')', ';' });
} else {
commentBound= scanner.scanBackward(sourceStart, CHeuristicScanner.UNBOUND, new char[] { '{', '}', ';' });
}
}
if (commentBound == CHeuristicScanner.NOT_FOUND) {
commentBound= -1; // unbound
}
sourceStart= Math.min(sourceStart, doc.getLineOffset(nameLine));
int commentStart= searchCommentBackward(doc, sourceStart, commentBound);
if (commentStart >= 0) {
sourceStart= commentStart;
} else {
int nextNonWS= scanner.findNonWhitespaceForward(commentBound+1, sourceStart);
if (nextNonWS != CHeuristicScanner.NOT_FOUND) {
int nextNonWSLine= doc.getLineOfOffset(nextNonWS);
int lineOffset= doc.getLineOffset(nextNonWSLine);
if (doc.get(lineOffset, nextNonWS - lineOffset).trim().isEmpty()) {
sourceStart= doc.getLineOffset(nextNonWSLine);
}
}
}
}
return sourceStart;
}
private int computeSourceEnd(IDocument doc, int start, IBinding binding, boolean isDefinition, boolean isKnR) throws BadLocationException {
int sourceEnd= start;
CHeuristicScanner scanner= new CHeuristicScanner(doc);
// Expand forward to the end of the definition/declaration
boolean searchBrace= false;
boolean searchSemi= false;
boolean searchComma= false;
if (binding instanceof ICompositeType && isDefinition || binding instanceof IEnumeration) {
searchBrace= true;
} else if (binding instanceof ICPPTemplateDefinition) {
searchBrace= true;
} else if (binding instanceof IFunction && isDefinition) {
searchBrace= true;
} else if (binding instanceof IParameter) {
if (isKnR) {
searchSemi= true;
} else {
searchComma= true;
}
} else if (binding instanceof IEnumerator || binding instanceof ICPPTemplateParameter) {
searchComma= true;
} else if (binding instanceof IVariable || binding instanceof ITypedef) {
searchSemi= true;
} else if (!isDefinition) {
searchSemi= true;
}
if (searchBrace) {
int brace= scanner.scanForward(start, 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(start, 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;
if (binding instanceof IParameter) {
bound= scanner.findClosingPeer(start, '(', ')');
} else if (binding instanceof ICPPTemplateParameter) {
bound= scanner.findClosingPeer(start, '<', '>');
} else if (binding instanceof IEnumerator) {
bound= scanner.findClosingPeer(start, '{', '}');
} else {
bound = CHeuristicScanner.NOT_FOUND;
}
if (bound == CHeuristicScanner.NOT_FOUND) {
bound= Math.min(doc.getLength(), start + 100);
}
int comma= scanner.scanForward(start, bound, ',');
if (comma == CHeuristicScanner.NOT_FOUND) {
// last argument
sourceEnd= bound;
} else {
sourceEnd= comma;
// expand region to include whole line if rest is comment
IRegion lineRegion= doc.getLineInformationOfOffset(sourceEnd);
int lineEnd= lineRegion.getOffset() + lineRegion.getLength();
int nextNonWS= scanner.findNonWhitespaceForwardInAnyPartition(sourceEnd + 1, lineEnd);
if (nextNonWS != CHeuristicScanner.NOT_FOUND) {
String contentType= TextUtilities.getContentType(doc, ICPartitions.C_PARTITIONING, nextNonWS, false);
if (ICPartitions.C_MULTI_LINE_COMMENT.equals(contentType) || ICPartitions.C_SINGLE_LINE_COMMENT.equals(contentType)) {
sourceEnd= lineEnd;
}
}
}
}
return sourceEnd;
}
/**
* 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 {@code null}
* @throws CoreException
*/
private IName[] findDefinitions(IASTTranslationUnit ast, IBinding binding) throws CoreException {
if (binding instanceof ICPPAliasTemplateInstance) {
binding = ((ICPPAliasTemplateInstance) binding).getTemplateDefinition();
}
IName[] declNames= ast.getDefinitionsInAST(binding);
if (declNames.length == 0 && ast.getIndex() != null) {
// search definitions in index
declNames = ast.getIndex().findNames(binding, IIndex.FIND_DEFINITIONS | IIndex.SEARCH_ACROSS_LANGUAGE_BOUNDARIES);
}
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 {@code 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 | IIndex.SEARCH_ACROSS_LANGUAGE_BOUNDARIES);
}
return declNames;
}
/**
* @return the computed source or {@code null}, if no source could be computed
*/
public String getSource() {
return fSource;
}
}
/**
*
*/
public CSourceHover() {
super();
}
@Override
public String getHoverInfo(ITextViewer textViewer, IRegion hoverRegion) {
IEditorPart editor = getEditor();
if (editor != null) {
IEditorInput input= editor.getEditorInput();
IWorkingCopyManager manager= CUIPlugin.getDefault().getWorkingCopyManager();
IWorkingCopy workingCopy = manager.getWorkingCopy(input);
try {
if (workingCopy == null || !workingCopy.isConsistent()) {
return null;
}
} catch (CModelException e) {
return null;
}
String expression;
try {
expression = textViewer.getDocument().get(hoverRegion.getOffset(), hoverRegion.getLength());
expression = expression.trim();
if (expression.isEmpty())
return null;
// Before trying a search lets make sure that the user is not hovering
// over a keyword other than 'auto'.
if (selectionIsKeyword(expression) && !expression.equals(Keywords.AUTO))
return null;
// Try with the indexer.
String source= searchInIndex(workingCopy, hoverRegion, expression);
if (source == null || source.trim().isEmpty())
return null;
// We are actually interested in the comments, too.
// source= removeLeadingComments(source);
String delim= System.getProperty("line.separator", "\n"); //$NON-NLS-1$ //$NON-NLS-2$
String[] sourceLines= Strings.convertIntoLines(source);
String firstLine= sourceLines[0];
boolean ignoreFirstLine= firstLine.length() > 0 && !Character.isWhitespace(firstLine.charAt(0));
Strings.trimIndentation(sourceLines, getTabWidth(), getTabWidth(), !ignoreFirstLine);
source = Strings.concatenate(sourceLines, delim);
return source;
} catch (BadLocationException e) {
}
}
return null;
}
/**
* 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 {@code -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 0;
}
ITypedRegion partition= TextUtilities.getPartition(doc, ICPartitions.C_PARTITIONING, start, true);
int currentOffset= Math.max(doc.getLineOffset(firstLine - 1), partition.getOffset() - 1);
int commentOffset= -1;
while (currentOffset > bound) {
partition= TextUtilities.getPartition(doc, ICPartitions.C_PARTITIONING, currentOffset, true);
currentOffset= partition.getOffset() - 1;
if (ICPartitions.C_MULTI_LINE_COMMENT.equals(partition.getType())
|| ICPartitions.C_MULTI_LINE_DOC_COMMENT.equals(partition.getType())) {
final int partitionOffset= partition.getOffset();
final int startLine= doc.getLineOfOffset(partitionOffset);
final int lineOffset= doc.getLineOffset(startLine);
if (partitionOffset == lineOffset ||
doc.get(lineOffset, partitionOffset - lineOffset).trim().isEmpty()) {
return lineOffset;
}
return commentOffset;
} else if (ICPartitions.C_SINGLE_LINE_COMMENT.equals(partition.getType())
|| ICPartitions.C_SINGLE_LINE_DOC_COMMENT.equals(partition.getType())) {
final int partitionOffset= partition.getOffset();
final int startLine= doc.getLineOfOffset(partitionOffset);
final int lineOffset= doc.getLineOffset(startLine);
if (partitionOffset == lineOffset ||
doc.get(lineOffset, partitionOffset - lineOffset).trim().isEmpty()) {
commentOffset= lineOffset;
continue;
}
return commentOffset;
} else if (IDocument.DEFAULT_CONTENT_TYPE.equals(partition.getType())) {
if (doc.get(partition.getOffset(), partition.getLength()).trim().isEmpty()) {
continue;
}
if (commentOffset >= 0) {
break;
}
} else {
break;
}
}
return commentOffset;
}
private static int getTabWidth() {
return 4;
}
/**
* 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;
try {
reader.configureForwardReader(document, 0, document.getLength(), true, false);
int c= reader.read();
while (c != -1 && (c == '\r' || c == '\n')) {
c= reader.read();
}
i= reader.getOffset();
reader.close();
} catch (IOException e) {
i= 0;
} finally {
try {
reader.close();
} catch (IOException e) {
CUIPlugin.log(e);
}
}
if (i < 0)
return source;
return source.substring(i);
}
protected String searchInIndex(final ITranslationUnit tUnit, IRegion textRegion, String selection) {
final ComputeSourceRunnable computer= new ComputeSourceRunnable(tUnit, textRegion, selection);
Job job= new Job(CHoverMessages.CSourceHover_jobTitle) {
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
return ASTProvider.getASTProvider().runOnAST(tUnit, ASTProvider.WAIT_ACTIVE_ONLY,
monitor, computer);
} catch (Throwable t) {
CUIPlugin.log(t);
}
return Status.CANCEL_STATUS;
}
};
// If the hover thread is interrupted this might have negative
// effects on the index - see http://bugs.eclipse.org/219834
// Therefore we schedule a job to decouple the parsing from this thread.
job.setPriority(Job.DECORATE);
job.setSystem(true);
job.setRule(SingletonRule.INSTANCE);
job.schedule();
try {
job.join();
} catch (InterruptedException e) {
job.cancel();
return null;
}
return computer.getSource();
}
/**
* Checks whether the given name is a known keyword.
*
* @param name
* @return {@code true} if the name is a known keyword or {@code false} if the
* name is not considered a keyword
*/
private boolean selectionIsKeyword(String name) {
Set<String> keywords= ParserFactory.getKeywordSet(KeywordSetKey.KEYWORDS, ParserLanguage.CPP);
return keywords.contains(name);
}
@Override
public IInformationControlCreator getHoverControlCreator() {
return new IInformationControlCreator() {
@Override
public IInformationControl createInformationControl(Shell parent) {
IEditorPart editor= getEditor();
int orientation= SWT.NONE;
if (editor instanceof IWorkbenchPartOrientation)
orientation= ((IWorkbenchPartOrientation) editor).getOrientation();
return new SourceViewerInformationControl(parent, false, orientation,
getTooltipAffordanceString());
}
};
}
@Override
public IInformationControlCreator getInformationPresenterControlCreator() {
return new IInformationControlCreator() {
@Override
public IInformationControl createInformationControl(Shell parent) {
IEditorPart editor= getEditor();
int orientation= SWT.NONE;
if (editor instanceof IWorkbenchPartOrientation)
orientation= ((IWorkbenchPartOrientation) editor).getOrientation();
return new SourceViewerInformationControl(parent, true, orientation, null);
}
};
}
}