-
Notifications
You must be signed in to change notification settings - Fork 411
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add hover for package fragments #84
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,62 +12,74 @@ | |
|
||
import java.io.IOException; | ||
import java.io.Reader; | ||
import java.util.stream.Stream; | ||
|
||
import org.eclipse.core.runtime.CoreException; | ||
import org.eclipse.jdt.core.IBuffer; | ||
import org.eclipse.jdt.core.IJavaElement; | ||
import org.eclipse.jdt.core.ILocalVariable; | ||
import org.eclipse.jdt.core.IMember; | ||
import org.eclipse.jdt.core.IPackageFragment; | ||
import org.eclipse.jdt.core.ISourceRange; | ||
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.internal.core.util.Util; | ||
import org.eclipse.jface.text.BadLocationException; | ||
import org.eclipse.jface.text.DefaultLineTracker; | ||
import org.eclipse.jface.text.Document; | ||
import org.eclipse.jface.text.IDocument; | ||
import org.eclipse.jface.text.ILineTracker; | ||
import org.eclipse.jface.text.IRegion; | ||
import org.jboss.tools.vscode.java.internal.handlers.JsonRpcHelpers; | ||
|
||
import copied.org.eclipse.jdt.ui.JavadocContentAccess; | ||
|
||
public class HoverInfoProvider { | ||
|
||
private final ITypeRoot unit; | ||
public HoverInfoProvider(ITypeRoot aUnit) { | ||
this.unit = aUnit; | ||
} | ||
|
||
public String computeHover(int line, int column) { | ||
try { | ||
IJavaElement[] elements = unit.codeSelect(JsonRpcHelpers.toOffset(unit.getBuffer(),line,column),0); | ||
if(elements == null || elements.length != 1) | ||
if(elements == null) { | ||
return null; | ||
IJavaElement curr= elements[0]; | ||
// return computeSourceHover(curr); | ||
} | ||
IJavaElement curr = null; | ||
if (elements.length != 1) { | ||
// they could be package fragments. | ||
// We need to select the one that matches the package fragment of the current unit | ||
IPackageFragment packageFragment = (IPackageFragment) unit.getParent(); | ||
IJavaElement found = | ||
Stream | ||
.of(elements) | ||
.filter(e -> e.equals(packageFragment)) | ||
.findFirst() | ||
.orElse(null); | ||
if (found == null) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not imply test packageFragment.getKind() == IPackageFragmentRoot.K_BINARY? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because I think this is too early to check that. It can be a package from source as well if you import a type from another package. This is a case where it doesn't match the package from the current compilation unit. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fair enough |
||
// this would be a binary package fragment | ||
return computeJavadocHover(elements[0]); | ||
} | ||
curr = found; | ||
} else { | ||
curr = elements[0]; | ||
} | ||
return computeJavadocHover(curr); | ||
|
||
} catch (JavaModelException e) { | ||
// TODO Auto-generated catch block | ||
} catch (CoreException e) { | ||
e.printStackTrace(); | ||
} | ||
return null; | ||
} | ||
|
||
private String computeJavadocHover(IJavaElement element) throws JavaModelException{ | ||
private String computeJavadocHover(IJavaElement element) throws CoreException { | ||
IMember member; | ||
if (element instanceof ILocalVariable) { | ||
member= ((ILocalVariable) element).getDeclaringMember(); | ||
} else if (element instanceof ITypeParameter) { | ||
member= ((ITypeParameter) element).getDeclaringMember(); | ||
} else if (element instanceof IMember) { | ||
member= (IMember) element; | ||
} else if (element instanceof IPackageFragment) { | ||
Reader r = JavadocContentAccess.getHTMLContentReader((IPackageFragment) element, true); | ||
if(r == null ) return null; | ||
return getString(r); | ||
} else { | ||
return null; | ||
} | ||
|
||
IBuffer buf= member.getOpenable().getBuffer(); | ||
if (buf == null) { | ||
return null; // no source attachment found | ||
|
@@ -79,75 +91,7 @@ private String computeJavadocHover(IJavaElement element) throws JavaModelExcepti | |
if(r == null ) return null; | ||
return getString(r); | ||
} | ||
|
||
|
||
/** | ||
* Returns source as hover | ||
* @param curr | ||
* @return | ||
*/ | ||
private String computeSourceHover(IJavaElement curr) { | ||
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= Util.getLineSeparator(source,unit.getJavaProject()); | ||
source= concatenate(sourceLines, delim); | ||
|
||
return source; | ||
} catch (JavaModelException ex) { | ||
//do nothing | ||
} | ||
} | ||
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= 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) { | ||
} | ||
} | ||
|
||
if (i < 0) | ||
return source; | ||
return source.substring(i); | ||
} | ||
/** | ||
* Gets the reader content as a String | ||
* | ||
|
@@ -166,46 +110,4 @@ private static String getString(Reader reader) { | |
} | ||
return buf.toString(); | ||
} | ||
|
||
/** | ||
* Converts the given string into an array of lines. The lines | ||
* don't contain any line delimiter characters. | ||
* | ||
* @param input the string | ||
* @return the string converted into an array of strings. Returns <code> | ||
* null</code> if the input string can't be converted in an array of lines. | ||
*/ | ||
public static String[] convertIntoLines(String input) { | ||
try { | ||
ILineTracker tracker= new DefaultLineTracker(); | ||
tracker.set(input); | ||
int size= tracker.getNumberOfLines(); | ||
String result[]= new String[size]; | ||
for (int i= 0; i < size; i++) { | ||
IRegion region= tracker.getLineInformation(i); | ||
int offset= region.getOffset(); | ||
result[i]= input.substring(offset, offset + region.getLength()); | ||
} | ||
return result; | ||
} catch (BadLocationException e) { | ||
return null; | ||
} | ||
} | ||
|
||
/** | ||
* Concatenate the given strings into one strings using the passed line delimiter as a | ||
* delimiter. No delimiter is added to the last line. | ||
* @param lines the lines | ||
* @param delimiter line delimiter | ||
* @return the concatenated lines | ||
*/ | ||
public static String concatenate(String[] lines, String delimiter) { | ||
StringBuilder buffer= new StringBuilder(); | ||
for (int i= 0; i < lines.length; i++) { | ||
if (i > 0) | ||
buffer.append(delimiter); | ||
buffer.append(lines[i]); | ||
} | ||
return buffer.toString(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,24 +8,35 @@ | |
* Contributors: | ||
* IBM Corporation - initial API and implementation | ||
*******************************************************************************/ | ||
package copied.org.eclipse.jdt.ui; | ||
package org.jboss.tools.vscode.java.internal; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. org.jboss.tools.vscode.java.internal.javadoc? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, doable. |
||
|
||
import java.io.IOException; | ||
import java.io.Reader; | ||
import java.io.StringReader; | ||
import java.util.Map; | ||
|
||
import org.eclipse.jdt.core.IBuffer; | ||
import org.eclipse.jdt.core.IJavaElement; | ||
import org.eclipse.jdt.core.IJavaModelStatusConstants; | ||
import org.eclipse.jdt.core.IJavaProject; | ||
import org.eclipse.jdt.core.IMember; | ||
import org.eclipse.jdt.core.IMethod; | ||
import org.eclipse.jdt.core.IPackageFragment; | ||
import org.eclipse.jdt.core.IPackageFragmentRoot; | ||
import org.eclipse.jdt.core.ISourceRange; | ||
import org.eclipse.jdt.core.IType; | ||
import org.eclipse.jdt.core.ITypeHierarchy; | ||
import org.eclipse.jdt.core.ITypeRoot; | ||
import org.eclipse.jdt.core.JavaCore; | ||
import org.eclipse.jdt.core.JavaModelException; | ||
import org.eclipse.jdt.core.dom.AST; | ||
import org.eclipse.jdt.core.dom.ASTParser; | ||
import org.eclipse.jdt.core.dom.CompilationUnit; | ||
import org.eclipse.jdt.core.dom.Javadoc; | ||
import org.eclipse.jdt.core.dom.PackageDeclaration; | ||
import org.jboss.tools.vscode.java.internal.hover.JavaDocCommentReader; | ||
import org.jboss.tools.vscode.javadoc.internal.JavaDoc2MarkdownConverter; | ||
|
||
import copied.org.eclipse.jdt.internal.corext.javadoc.JavaDocCommentReader; | ||
import copied.org.eclipse.jdt.internal.corext.util.MethodOverrideTester; | ||
|
||
/** | ||
|
@@ -41,6 +52,15 @@ | |
* @noextend This class is not intended to be subclassed by clients. | ||
*/ | ||
public class JavadocContentAccess { | ||
/** | ||
* The name of the package-info.java file. | ||
*/ | ||
public static final String PACKAGE_INFO_JAVA= "package-info.java"; //$NON-NLS-1$ | ||
|
||
/** | ||
* The name of the package-info.class file. | ||
*/ | ||
public static final String PACKAGE_INFO_CLASS= "package-info.class"; //$NON-NLS-1$ | ||
|
||
private JavadocContentAccess() { | ||
// do not instantiate | ||
|
@@ -92,6 +112,66 @@ private static Reader internalGetContentReader(IMember member) throws JavaModelE | |
return null; | ||
} | ||
|
||
/** | ||
* Gets a reader for an package fragment's Javadoc comment content from the source attachment. | ||
* The content does contain only the text from the comment without the Javadoc leading star characters. | ||
* Returns <code>null</code> if the package fragment does not contain a Javadoc comment or if no source is available. | ||
* @param fragment The package fragment to get the Javadoc of. | ||
* @return Returns a reader for the Javadoc comment content or <code>null</code> if the member | ||
* does not contain a Javadoc comment or if no source is available | ||
* @throws JavaModelException is thrown when the package fragment's javadoc can not be accessed | ||
* @since 3.4 | ||
*/ | ||
private static Reader internalGetContentReader(IPackageFragment fragment) throws JavaModelException { | ||
IPackageFragmentRoot root= (IPackageFragmentRoot) fragment.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT); | ||
|
||
//1==> Handle the case when the documentation is present in package-info.java or package-info.class file | ||
boolean isBinary= root.getKind() == IPackageFragmentRoot.K_BINARY; | ||
ITypeRoot packageInfo; | ||
if (isBinary) { | ||
packageInfo= fragment.getClassFile(PACKAGE_INFO_CLASS); | ||
} else { | ||
packageInfo= fragment.getCompilationUnit(PACKAGE_INFO_JAVA); | ||
} | ||
if (packageInfo != null && packageInfo.exists()) { | ||
String source = packageInfo.getSource(); | ||
//the source can be null for some of the class files | ||
if (source != null) { | ||
Javadoc javadocNode = getPackageJavadocNode(fragment, source); | ||
if (javadocNode != null) { | ||
int start = javadocNode.getStartPosition(); | ||
int length = javadocNode.getLength(); | ||
return new JavaDocCommentReader(source, start, start + length - 1); | ||
} | ||
} | ||
} | ||
return null; | ||
} | ||
|
||
private static Javadoc getPackageJavadocNode(IJavaElement element, String cuSource) { | ||
CompilationUnit cu= createAST(element, cuSource); | ||
if (cu != null) { | ||
PackageDeclaration packDecl= cu.getPackage(); | ||
if (packDecl != null) { | ||
return packDecl.getJavadoc(); | ||
} | ||
} | ||
return null; | ||
} | ||
|
||
private static CompilationUnit createAST(IJavaElement element, String cuSource) { | ||
ASTParser parser= ASTParser.newParser(AST.JLS8); | ||
|
||
IJavaProject javaProject= element.getJavaProject(); | ||
parser.setProject(javaProject); | ||
Map<String, String> options= javaProject.getOptions(true); | ||
options.put(JavaCore.COMPILER_DOC_COMMENT_SUPPORT, JavaCore.ENABLED); // workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=212207 | ||
parser.setCompilerOptions(options); | ||
|
||
parser.setSource(cuSource.toCharArray()); | ||
return (CompilationUnit) parser.createAST(null); | ||
} | ||
|
||
/** | ||
* Checks whether the given reader only returns | ||
* the inheritDoc tag. | ||
|
@@ -149,6 +229,49 @@ public static Reader getHTMLContentReader(IMember member, boolean allowInherited | |
return null; | ||
} | ||
|
||
/** | ||
* Gets a reader for a package fragment's Javadoc comment content from the source attachment. | ||
* and renders the tags in HTML. | ||
* Returns <code>null</code> if the package fragment does not contain a Javadoc comment or if no source is available. | ||
* | ||
* @param fragment the package fragment to get the Javadoc of. | ||
* @param useAttachedJavadoc if <code>true</code> Javadoc will be extracted from attached Javadoc | ||
* if there's no source | ||
* @return a reader for the Javadoc comment content in HTML or <code>null</code> if the package fragment | ||
* does not contain a Javadoc comment or if no source is available | ||
* @throws JavaModelException is thrown when the package fragment's Javadoc can not be accessed | ||
* @since 3.2 | ||
*/ | ||
public static Reader getHTMLContentReader(IPackageFragment fragment, boolean useAttachedJavadoc) throws JavaModelException { | ||
Reader contentReader= internalGetContentReader(fragment); | ||
if (contentReader != null) { | ||
try { | ||
return new JavaDoc2MarkdownConverter(contentReader).getAsReader(); | ||
} catch (IOException e) { | ||
throw new JavaModelException(e, IJavaModelStatusConstants.UNKNOWN_JAVADOC_FORMAT); | ||
} | ||
} | ||
if (useAttachedJavadoc) { | ||
// only if no source available | ||
// check parent | ||
IPackageFragmentRoot root= (IPackageFragmentRoot) fragment.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT); | ||
|
||
//1==> Handle the case when the documentation is present in package-info.java or package-info.class file | ||
boolean isBinary= root.getKind() == IPackageFragmentRoot.K_BINARY; | ||
if (isBinary) { | ||
String s= fragment.getAttachedJavadoc(null); | ||
if (s != null) { | ||
try { | ||
return new JavaDoc2MarkdownConverter(new StringReader(s)).getAsReader(); | ||
} catch (IOException e) { | ||
throw new JavaModelException(e, IJavaModelStatusConstants.UNKNOWN_JAVADOC_FORMAT); | ||
} | ||
} | ||
} | ||
} | ||
return null; | ||
} | ||
|
||
private static Reader findDocInHierarchy(IMethod method, boolean isHTML, boolean useAttachedJavadoc) throws JavaModelException { | ||
/* | ||
* Catch ExternalJavaProject in which case | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
potential IIOOBE if length == 0, L54 and L58
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll check that.