blob: 4703af9ef6b9ed8d2e6b2ec3354f7ef7df299ca0 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2012 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
*******************************************************************************/
package org.eclipse.jdt.internal.ui.text.java.hover;
import java.io.IOException;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
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.ui.IEditorPart;
import org.eclipse.ui.part.IWorkbenchPartOrientation;
import org.eclipse.ui.editors.text.EditorsUI;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.ILocalVariable;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.ISourceReference;
import org.eclipse.jdt.core.ITypeParameter;
import org.eclipse.jdt.core.ITypeRoot;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Javadoc;
import org.eclipse.jdt.core.dom.NodeFinder;
import org.eclipse.jdt.core.dom.SwitchStatement;
import org.eclipse.jdt.internal.corext.codemanipulation.StubUtility;
import org.eclipse.jdt.internal.corext.dom.ASTNodes;
import org.eclipse.jdt.internal.corext.util.Messages;
import org.eclipse.jdt.internal.corext.util.Strings;
import org.eclipse.jdt.ui.SharedASTProvider;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor;
import org.eclipse.jdt.internal.ui.text.JavaCodeReader;
import org.eclipse.jdt.internal.ui.text.JavaPairMatcher;
/**
* Provides as hover info the source of the selected JavaElement, or the source near the matching
* opening curly brace.
*/
public class JavaSourceHover extends AbstractJavaEditorTextHover {
/**
* The upward shift in location in lines for the bracket hover.
*
* @since 3.8
*/
private int fUpwardShiftInLines;
/**
* The status text for the bracket hover.
*
* @since 3.8
*/
private String fBracketHoverStatus;
/*
* @see JavaElementHover
*/
@Deprecated
public String getHoverInfo(ITextViewer textViewer, IRegion region) {
IJavaElement[] result= getJavaElementsAt(textViewer, region);
fUpwardShiftInLines= 0;
fBracketHoverStatus= null;
if (result == null || result.length == 0) {
return getBracketHoverInfo(textViewer, region);
}
if (result.length > 1)
return null;
IJavaElement curr= result[0];
if ((curr instanceof IMember || curr instanceof ILocalVariable || curr instanceof ITypeParameter) && curr instanceof ISourceReference) {
try {
String source= ((ISourceReference) curr).getSource();
String[] sourceLines= getTrimmedSource(source, curr);
if (sourceLines == null)
return null;
String delim= StubUtility.getLineDelimiterUsed(curr);
source= Strings.concatenate(sourceLines, delim);
return source;
} catch (JavaModelException ex) {
//do nothing
}
}
return null;
}
private String getBracketHoverInfo(final ITextViewer textViewer, IRegion region) {
IEditorPart editor= getEditor();
ITypeRoot editorInput= getEditorInputJavaElement();
if (!(editor instanceof JavaEditor) || editorInput == null) {
return null;
}
int offset= region.getOffset();
IDocument document= textViewer.getDocument();
if (document == null)
return null;
try {
char c= document.getChar(offset);
if (c != '}')
return null;
JavaPairMatcher matcher= ((JavaEditor) editor).getBracketMatcher();
if (matcher == null)
return null;
IRegion match= matcher.match(document, offset);
if (match == null)
return null;
String delim= StubUtility.getLineDelimiterUsed(editorInput);
CompilationUnit ast= SharedASTProvider.getAST(editorInput, SharedASTProvider.WAIT_NO, null);
if (ast == null)
return null;
ASTNode bracketNode= NodeFinder.perform(ast, match.getOffset(),
match.getLength());
if (bracketNode == null)
return null;
ASTNode node;
ASTNode parent= bracketNode.getParent();
if (bracketNode instanceof Block && !(parent instanceof Block) && !(parent instanceof SwitchStatement)) {
node= parent;
} else {
node= bracketNode;
}
int nodeStart;
int nodeLength;
if (node instanceof BodyDeclaration) {
BodyDeclaration declaration= (BodyDeclaration) node;
Javadoc javadoc= declaration.getJavadoc();
int lengthOfJavadoc= javadoc == null ? 0 : javadoc.getLength() +
delim.length();
nodeStart= node.getStartPosition() + lengthOfJavadoc;
nodeLength= node.getLength() - lengthOfJavadoc;
} else {
nodeStart= node.getStartPosition();
nodeLength= ASTNodes.getExclusiveEnd(bracketNode) - nodeStart;
}
int line1= document.getLineOfOffset(nodeStart);
int sourceOffset= document.getLineOffset(line1);
int line2= document.getLineOfOffset(nodeStart + nodeLength);
int hoveredLine= document.getLineOfOffset(offset);
if (line2 > hoveredLine)
line2= hoveredLine;
//check if line1 is visible
final int[] topIndex= new int[1];
StyledText textWidget= textViewer.getTextWidget();
if (textWidget == null)
return null;
Display display;
try {
display= textWidget.getDisplay();
} catch (SWTException ex) {
if (ex.code == SWT.ERROR_WIDGET_DISPOSED)
return null;
else
throw ex;
}
display.syncExec(new Runnable() {
public void run() {
topIndex[0]= textViewer.getTopIndex();
}
});
int topLine= topIndex[0];
if (topLine == -1)
return null;
int noOfSourceLines;
IRegion endLine;
if (line1 < topLine) {
//match not visible
noOfSourceLines= 3;
int skippedLines= Math.abs(line2 - line1 - noOfSourceLines);
if (skippedLines == 1) {
noOfSourceLines++;
skippedLines= 0;
}
endLine= document.getLineInformation(line1 + noOfSourceLines);
fUpwardShiftInLines= noOfSourceLines;
if (skippedLines > 0) {
fBracketHoverStatus= Messages.format(JavaHoverMessages.JavaSourceHover_skippedLines, new Integer(skippedLines));
}
} else {
noOfSourceLines= line2 - line1;
endLine= document.getLineInformation(line2);
fUpwardShiftInLines= line2 - line1;
}
if (fUpwardShiftInLines == 0)
return null;
int sourceLength= (endLine.getOffset() + endLine.getLength()) - sourceOffset;
String source= document.get(sourceOffset, sourceLength);
String[] sourceLines= getTrimmedSource(source, editorInput);
if (sourceLines == null)
return null;
String[] str= new String[noOfSourceLines];
System.arraycopy(sourceLines, 0, str, 0, noOfSourceLines);
source= Strings.concatenate(str, delim);
return source;
} catch (BadLocationException e) {
JavaPlugin.log(e);
return null;
}
}
/**
* Returns the trimmed source lines.
*
* @param source the source string, could be <code>null</code>
* @param javaElement the java element
* @return the trimmed source lines or <code>null</code>
*/
private String[] getTrimmedSource(String source, IJavaElement javaElement) {
if (source == null)
return null;
source= removeLeadingComments(source);
String[] sourceLines= Strings.convertIntoLines(source);
Strings.trimIndentation(sourceLines, javaElement.getJavaProject());
return sourceLines;
}
private String removeLeadingComments(String source) {
final JavaCodeReader reader= new JavaCodeReader();
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) {
JavaPlugin.log(ex);
}
}
if (i < 0)
return source;
return source.substring(i);
}
/*
* @see org.eclipse.jface.text.ITextHoverExtension#getHoverControlCreator()
* @since 3.0
*/
@Override
public IInformationControlCreator getHoverControlCreator() {
if (fUpwardShiftInLines > 0)
return createInformationControlCreator(false, fBracketHoverStatus, true);
else
return createInformationControlCreator(false, EditorsUI.getTooltipAffordanceString(), true);
}
/*
* @see org.eclipse.jdt.internal.ui.text.java.hover.AbstractJavaEditorTextHover#getInformationPresenterControlCreator()
* @since 3.0
*/
@Override
public IInformationControlCreator getInformationPresenterControlCreator() {
if (fUpwardShiftInLines > 0)
return createInformationControlCreator(false, fBracketHoverStatus, true);
else
return createInformationControlCreator(true, EditorsUI.getTooltipAffordanceString(), true);
}
/**
* Returns the information control creator.
*
* @param isResizable <code>true</code> if resizable
* @param statusFieldText the text to be used in the optional status field or <code>null</code>
* if the status field should be hidden
* @param doShiftUp <code>true</code> iff {@link #fUpwardShiftInLines} should be considered
* @return the information control creator
* @since 3.8
*/
private IInformationControlCreator createInformationControlCreator(final boolean isResizable, final String statusFieldText, final boolean doShiftUp) {
return new IInformationControlCreator() {
public IInformationControl createInformationControl(final Shell parent) {
final IEditorPart editor= getEditor();
int orientation= SWT.NONE;
if (editor instanceof IWorkbenchPartOrientation)
orientation= ((IWorkbenchPartOrientation) editor).getOrientation();
return new SourceViewerInformationControl(parent, isResizable, orientation, statusFieldText) {
@Override
public void setLocation(Point location) {
Point loc= location;
if (doShiftUp && fUpwardShiftInLines > 0) {
Point size= super.computeSizeConstraints(0, fUpwardShiftInLines + 1);
//bracket hover is rendered above '}'
int y= location.y - size.y - 5; //AbstractInformationControlManager.fMarginY = 5
Rectangle trim= computeTrim();
loc= new Point(location.x + trim.x - getViewer().getTextWidget().getLeftMargin(), y - trim.height - trim.y);
}
super.setLocation(loc);
}
@Override
public Point computeSizeConstraints(int widthInChars, int heightInChars) {
if (doShiftUp && fUpwardShiftInLines > 0) {
Point sizeConstraints= super.computeSizeConstraints(widthInChars, heightInChars);
return new Point(sizeConstraints.x, 0); //set height as 0 to ensure selection of bottom anchor in AbstractInformationControlManager.computeInformationControlLocation(...)
} else {
return super.computeSizeConstraints(widthInChars, heightInChars);
}
}
@Override
public void setSize(int width, int height) {
if (doShiftUp && fUpwardShiftInLines != 0) {
//compute the correct height of hover, this was set to 0 in computeSizeConstraints(..)
Point size= super.computeSizeConstraints(0, fUpwardShiftInLines);
Rectangle trim= computeTrim();
super.setSize(width, size.y + trim.height - trim.y);
} else {
super.setSize(width, height);
}
}
@Override
public IInformationControlCreator getInformationPresenterControlCreator() {
if (doShiftUp && fUpwardShiftInLines > 0) {
// Hack: We don't wan't to have auto-enrichment when the mouse moves into the hover,
// but we do want F2 to persist the hover. The framework has no way to distinguish the
// two requests, so we have to implement this aspect.
for (StackTraceElement element : Thread.currentThread().getStackTrace()) {
if ("canMoveIntoInformationControl".equals(element.getMethodName()) //$NON-NLS-1$
&& "org.eclipse.jface.text.AbstractHoverInformationControlManager".equals(element.getClassName())) //$NON-NLS-1$
return null; //do not enrich bracket hover
}
return JavaSourceHover.this.createInformationControlCreator(isResizable && !isResizable, statusFieldText, false);
} else {
return super.getInformationPresenterControlCreator();
}
}
};
}
};
}
}