/*******************************************************************************
* Copyright (c) 2002, 2011 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)
*******************************************************************************/
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.DOMException;
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.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.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.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.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.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();
public boolean contains(ISchedulingRule rule) {
return rule == this;
}
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 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(ILanguage lang, IASTTranslationUnit ast) {
if (ast != null) {
try {
IASTName name= ast.getNodeSelector(null).findEnclosingName(fTextRegion.getOffset(), fTextRegion.getLength());
if (name != null) {
IBinding binding= name.resolveBinding();
if (binding != null) {
// Check for implicit names first, could be an implicit constructor call
if(name.getParent() instanceof IASTImplicitNameOwner) {
IASTImplicitNameOwner iastImplicitNameOwner = (IASTImplicitNameOwner) name.getParent();
IASTName [] implicitNames = iastImplicitNameOwner.getImplicitNames();
if(implicitNames.length == 1) {
IBinding implicitNameBinding = implicitNames[0].resolveBinding();
if(implicitNameBinding instanceof ICPPConstructor) {
binding = implicitNameBinding;
}
}
}
if (binding instanceof IProblemBinding) {
// report problem as source comment
if (DEBUG) {
IProblemBinding problem= (IProblemBinding) binding;
fSource= "/* Indexer 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 {
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 <code>#define</code> 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</code>
* @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;
}
/**
* 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</code>, 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;
}
/**
* 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 <code>null</code>, 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 null;
}
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 exc) {
// ignore - should not happen anyway
if (DEBUG) exc.printStackTrace();
} finally {
mgr.disconnect(location, LocationKind.LOCATION, fMonitor);
}
return null;
}
/**
* Determine if the name is part of a KnR function definition.
* @param name
* @return <code>true</code> 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().length() == 0) {
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</code>
* @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().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</code>
* @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</code>, if no source could be computed
*/
public String getSource() {
return fSource;
}
}
/**
*
*/
public CSourceHover() {
super();
}
/*
* @see ITextHover#getHoverInfo(ITextViewer, IRegion)
*/
@Override
public String getHoverInfo(ITextViewer textViewer, IRegion hoverRegion) {
IEditorPart editor = getEditor();
if (editor != null) {
IEditorInput input= editor.getEditorInput();
IWorkingCopyManager manager= CUIPlugin.getDefault().getWorkingCopyManager();
IWorkingCopy copy = manager.getWorkingCopy(input);
try {
if (copy == null || !copy.isConsistent()) {
return null;
}
} catch (CModelException exc) {
return null;
}
String expression;
try {
expression = textViewer.getDocument().get(hoverRegion.getOffset(), hoverRegion.getLength());
expression = expression.trim();
if (expression.length() == 0)
return null;
//Before trying a search lets make sure that the user is not hovering over a keyword
if (selectionIsKeyword(expression))
return null;
String source= null;
// Try with the indexer
source= searchInIndex(copy, hoverRegion);
if (source == null || source.trim().length() == 0)
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];
if (firstLine.length() > 0 && !Character.isWhitespace(firstLine.charAt(0)))
sourceLines[0]= ""; //$NON-NLS-1$
Strings.trimIndentation(sourceLines, getTabWidth(), getTabWidth());
if (!Character.isWhitespace(firstLine.charAt(0)))
sourceLines[0]= firstLine;
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</code>, 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().length() == 0) {
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().length() == 0) {
commentOffset= lineOffset;
continue;
}
return commentOffset;
} else if (IDocument.DEFAULT_CONTENT_TYPE.equals(partition.getType())) {
if (doc.get(partition.getOffset(), partition.getLength()).trim().length() == 0) {
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 ex) {
i= 0;
} finally {
try {
reader.close();
} catch (IOException ex) {
CUIPlugin.log(ex);
}
}
if (i < 0)
return source;
return source.substring(i);
}
protected String searchInIndex(final ITranslationUnit tUnit, IRegion textRegion) {
final ComputeSourceRunnable computer= new ComputeSourceRunnable(tUnit, textRegion);
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 exc) {
job.cancel();
return null;
}
return computer.getSource();
}
/**
* Test whether the given name is a known keyword.
*
* @param name
* @return <code>true</code> if the name is a known keyword or <code>false</code> 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);
}
/*
* @see org.eclipse.jface.text.ITextHoverExtension#getHoverControlCreator()
* @since 3.0
*/
@Override
public IInformationControlCreator getHoverControlCreator() {
return new IInformationControlCreator() {
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());
}
};
}
/*
* @see IInformationProviderExtension2#getInformationPresenterControlCreator()
* @since 3.0
*/
@Override
public IInformationControlCreator getInformationPresenterControlCreator() {
return new IInformationControlCreator() {
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);
}
};
}
}