mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-13 09:19:13 +07:00
[Java] When we complete class name after new then if constructor(s) exist: show documentation for it, for several constructors suggest list of links to particular constructor documentation, same way as we do for (overloaded) calls.
Also try hard for documentation to work on incomplete new expressions like 'new Class<caret>', 'new Class(<caret>)'. As useful side effect method documentation links are shown for overloads (or method documentation for no overloads) when documentation action is invoked in argument list.
This commit is contained in:
@@ -48,6 +48,7 @@ import com.intellij.psi.javadoc.PsiDocComment;
|
||||
import com.intellij.psi.javadoc.PsiDocTag;
|
||||
import com.intellij.psi.util.PsiFormatUtil;
|
||||
import com.intellij.psi.util.PsiFormatUtilBase;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.HashMap;
|
||||
@@ -477,11 +478,55 @@ public class JavaDocumentationProvider implements CodeDocumentationProvider, Ext
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateDoc(final PsiElement element, final PsiElement originalElement) {
|
||||
public String generateDoc(PsiElement element, final PsiElement originalElement) {
|
||||
if (element instanceof PsiExpressionList) {
|
||||
element = element.getParent(); // for new Class(<caret>) or methodCall(<caret>) proceed from method call or new expression
|
||||
}
|
||||
if (element instanceof PsiMethodCallExpression) {
|
||||
return getMethodCandidateInfo((PsiMethodCallExpression)element);
|
||||
}
|
||||
|
||||
// Try hard for documentation of incomplete new Class instantiation
|
||||
PsiElement elt = originalElement != null ? PsiTreeUtil.prevLeaf(originalElement): element;
|
||||
if (elt instanceof PsiErrorElement) elt = elt.getPrevSibling();
|
||||
else if (elt != null && !(elt instanceof PsiNewExpression)) {
|
||||
elt = elt.getParent();
|
||||
}
|
||||
if (elt instanceof PsiNewExpression) {
|
||||
PsiClass targetClass = null;
|
||||
|
||||
if (element instanceof PsiJavaCodeReferenceElement) { // new Class<caret>
|
||||
PsiElement resolve = ((PsiJavaCodeReferenceElement)element).resolve();
|
||||
if (resolve instanceof PsiClass) targetClass = (PsiClass)resolve;
|
||||
} else if (element instanceof PsiClass) { //Class in completion
|
||||
targetClass = (PsiClass)element;
|
||||
} else if (element instanceof PsiNewExpression) { // new Class(<caret>)
|
||||
PsiJavaCodeReferenceElement reference = ((PsiNewExpression)element).getClassReference();
|
||||
if (reference != null) {
|
||||
PsiElement resolve = reference.resolve();
|
||||
if (resolve instanceof PsiClass) targetClass = (PsiClass)resolve;
|
||||
}
|
||||
}
|
||||
|
||||
if (targetClass != null) {
|
||||
PsiMethod[] constructors = targetClass.getConstructors();
|
||||
if (constructors.length > 0) {
|
||||
if (constructors.length == 1) return generateDoc(constructors[0], originalElement);
|
||||
@NonNls final StringBuilder sb = new StringBuilder();
|
||||
|
||||
for(PsiMethod constructor:constructors) {
|
||||
final String str = PsiFormatUtil.formatMethod(constructor, PsiSubstitutor.EMPTY,
|
||||
PsiFormatUtilBase.SHOW_NAME |
|
||||
PsiFormatUtilBase.SHOW_TYPE |
|
||||
PsiFormatUtilBase.SHOW_PARAMETERS,
|
||||
PsiFormatUtilBase.SHOW_TYPE | PsiFormatUtilBase.SHOW_NAME);
|
||||
createElementLink(sb, targetClass, StringUtil.escapeXml(str));
|
||||
}
|
||||
|
||||
return CodeInsightBundle.message("javadoc.constructor.candidates", targetClass.getName(), sb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//external documentation finder
|
||||
return generateExternalJavadoc(element);
|
||||
@@ -514,11 +559,15 @@ public class JavaDocumentationProvider implements CodeDocumentationProvider, Ext
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String getMethodCandidateInfo(PsiMethodCallExpression expr) {
|
||||
private String getMethodCandidateInfo(PsiMethodCallExpression expr) {
|
||||
final PsiResolveHelper rh = JavaPsiFacade.getInstance(expr.getProject()).getResolveHelper();
|
||||
final CandidateInfo[] candidates = rh.getReferencedMethodCandidates(expr, true);
|
||||
final String text = expr.getText();
|
||||
if (candidates.length > 0) {
|
||||
if (candidates.length == 1) {
|
||||
PsiElement element = candidates[0].getElement();
|
||||
if (element instanceof PsiMethod) return generateDoc(element, null);
|
||||
}
|
||||
@NonNls final StringBuilder sb = new StringBuilder();
|
||||
|
||||
for (final CandidateInfo candidate : candidates) {
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
public class Test1 {
|
||||
public void foo() {
|
||||
new Tim<caret>
|
||||
}
|
||||
}
|
||||
@@ -15,14 +15,68 @@
|
||||
*/
|
||||
package com.intellij.codeInsight
|
||||
|
||||
import com.intellij.codeInsight.documentation.DocumentationManager
|
||||
import com.intellij.codeInsight.navigation.CtrlMouseHandler
|
||||
import com.intellij.lang.java.JavaDocumentationProvider
|
||||
import com.intellij.psi.PsiExpressionList
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase
|
||||
|
||||
/**
|
||||
* @author peter
|
||||
*/
|
||||
class JavaDocumentationTest extends LightCodeInsightFixtureTestCase {
|
||||
|
||||
public void testConstructorDoc() {
|
||||
myFixture.configureByText 'a.java', '''
|
||||
class Foo { Foo() {} Foo(int param) {} }
|
||||
|
||||
class Foo2 {{
|
||||
new Foo<caret>
|
||||
}}
|
||||
'''
|
||||
def originalElement = myFixture.file.findElementAt(myFixture.editor.caretModel.offset)
|
||||
def doc = new JavaDocumentationProvider().generateDoc(
|
||||
DocumentationManager.getInstance(project).findTargetElement(myFixture.editor, myFixture.file),
|
||||
originalElement
|
||||
)
|
||||
|
||||
assert doc == """<html>Candidates for new <b>Foo</b>() are:<br> <a href="psi_element://Foo">Foo()</a><br> <a href="psi_element://Foo">Foo(int param)</a><br></html>"""
|
||||
}
|
||||
|
||||
public void testConstructorDoc2() {
|
||||
myFixture.configureByText 'a.java', '''
|
||||
class Foo { Foo() {} Foo(int param) {} }
|
||||
|
||||
class Foo2 {{
|
||||
new Foo(<caret>)
|
||||
}}
|
||||
'''
|
||||
def exprList = PsiTreeUtil.getParentOfType(myFixture.file.findElementAt(myFixture.editor.caretModel.offset), PsiExpressionList.class)
|
||||
def doc = new JavaDocumentationProvider().generateDoc(
|
||||
exprList,
|
||||
null
|
||||
)
|
||||
|
||||
assert doc == """<html>Candidates for new <b>Foo</b>() are:<br> <a href="psi_element://Foo">Foo()</a><br> <a href="psi_element://Foo">Foo(int param)</a><br></html>"""
|
||||
}
|
||||
|
||||
public void testMethodDocWhenInArgList() {
|
||||
myFixture.configureByText 'a.java', '''
|
||||
class Foo { void doFoo() {} }
|
||||
|
||||
class Foo2 {{
|
||||
new Foo().doFoo(<caret>)
|
||||
}}
|
||||
'''
|
||||
def exprList = PsiTreeUtil.getParentOfType(myFixture.file.findElementAt(myFixture.editor.caretModel.offset), PsiExpressionList.class)
|
||||
def doc = new JavaDocumentationProvider().generateDoc(
|
||||
exprList,
|
||||
null
|
||||
)
|
||||
|
||||
assert doc == """<html><head> <style type="text/css"> #error { background-color: #eeeeee; margin-bottom: 10px; } p { margin: 5px 0; } </style></head><body><small><b><a href="psi_element://Foo"><code>Foo</code></a></b></small><PRE>void <b>doFoo</b>()</PRE></body></html>"""
|
||||
}
|
||||
|
||||
public void testGenericMethod() {
|
||||
myFixture.configureByText 'a.java', '''
|
||||
class Bar<T> { java.util.List<T> foo(T param); }
|
||||
|
||||
+18
@@ -19,11 +19,13 @@ import com.intellij.JavaTestUtil;
|
||||
import com.intellij.codeInsight.lookup.LookupManager;
|
||||
import com.intellij.codeInsight.template.impl.TemplateManagerImpl;
|
||||
import com.intellij.codeInsight.template.impl.TemplateState;
|
||||
import com.intellij.lang.java.JavaDocumentationProvider;
|
||||
import com.intellij.openapi.command.WriteCommandAction;
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager;
|
||||
import com.intellij.openapi.roots.LanguageLevelProjectExtension;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.pom.java.LanguageLevel;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.testFramework.TestDataPath;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -58,6 +60,22 @@ public class ClassNameCompletionTest extends LightFixtureCompletionTestCase {
|
||||
checkResultByFile(path + "/after2.java");
|
||||
}
|
||||
|
||||
public void testDocAfterNew() throws Exception {
|
||||
createClass("public class Time { Time() {} Time(long time) {} }");
|
||||
|
||||
String path = "/docAfterNew";
|
||||
|
||||
configureByFile(path + "/before1.java");
|
||||
assertTrue(myItems != null && myItems.length >= 1);
|
||||
String doc = new JavaDocumentationProvider().generateDoc(
|
||||
(PsiClass)myItems[0].getObject(),
|
||||
myFixture.getFile().findElementAt(myFixture.getEditor().getCaretModel().getOffset())
|
||||
);
|
||||
|
||||
assertEquals(doc,
|
||||
"<html>Candidates for new <b>Time</b>() are:<br> <a href=\"psi_element://Time\">Time()</a><br> <a href=\"psi_element://Time\">Time(long time)</a><br></html>");
|
||||
}
|
||||
|
||||
public void testTypeParametersTemplate() throws Exception {
|
||||
createClass("package pack; public interface Foo<T> {void foo(T t};");
|
||||
|
||||
|
||||
+10
-43
@@ -91,7 +91,7 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp
|
||||
private static final String DOCUMENTATION_AUTO_UPDATE_ENABLED = "DocumentationAutoUpdateEnabled";
|
||||
|
||||
private Editor myEditor = null;
|
||||
private ParameterInfoController myParameterInfoController;
|
||||
private PsiElement myExpressionList;
|
||||
private final Alarm myUpdateDocAlarm;
|
||||
private WeakReference<JBPopup> myDocInfoHintRef;
|
||||
private Component myPreviouslyFocused = null;
|
||||
@@ -294,23 +294,17 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp
|
||||
if (list != null) {
|
||||
LookupEx lookup = LookupManager.getInstance(myProject).getActiveLookup();
|
||||
if (lookup != null) {
|
||||
myParameterInfoController = null; // take completion variants for documentation then
|
||||
myExpressionList = null; // take completion variants for documentation then
|
||||
} else {
|
||||
myParameterInfoController = ParameterInfoController.findControllerAtOffset(editor, list.getTextRange().getStartOffset());
|
||||
myExpressionList = list;
|
||||
}
|
||||
}
|
||||
|
||||
final PsiElement originalElement = getContextElement(editor, file);
|
||||
PsiElement element = assertSameProject(findTargetElement(editor, file));
|
||||
|
||||
if (element == null && myParameterInfoController != null) {
|
||||
final Object[] objects = myParameterInfoController.getSelectedElements();
|
||||
|
||||
if (objects != null && objects.length > 0) {
|
||||
if (objects[0] instanceof PsiElement) {
|
||||
element = assertSameProject((PsiElement)objects[0]);
|
||||
}
|
||||
}
|
||||
if (element == null && myExpressionList != null) {
|
||||
element = myExpressionList;
|
||||
}
|
||||
|
||||
if (element == null && file == null) return; //file == null for text field editor
|
||||
@@ -493,7 +487,7 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp
|
||||
Disposer.dispose(component);
|
||||
myEditor = null;
|
||||
myPreviouslyFocused = null;
|
||||
myParameterInfoController = null;
|
||||
myExpressionList = null;
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
})
|
||||
@@ -1065,12 +1059,12 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp
|
||||
}
|
||||
}
|
||||
);
|
||||
if (myParameterInfoController != null) {
|
||||
if (myExpressionList != null) {
|
||||
final String doc = ApplicationManager.getApplication().runReadAction(
|
||||
new NullableComputable<String>() {
|
||||
@Override
|
||||
public String compute() {
|
||||
return generateParameterInfoDocumentation(provider);
|
||||
return generateDocumentation(provider);
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -1113,35 +1107,8 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String generateParameterInfoDocumentation(DocumentationProvider provider) {
|
||||
final Object[] objects = myParameterInfoController.getSelectedElements();
|
||||
|
||||
if (objects.length > 0) {
|
||||
@NonNls StringBuffer sb = null;
|
||||
|
||||
for (Object o : objects) {
|
||||
PsiElement parameter = null;
|
||||
if (o instanceof PsiElement) {
|
||||
parameter = (PsiElement)o;
|
||||
}
|
||||
|
||||
if (parameter != null) {
|
||||
final SmartPsiElementPointer originalElement = parameter.getUserData(ORIGINAL_ELEMENT_KEY);
|
||||
final String str2 = provider.generateDoc(parameter, originalElement != null ? originalElement.getElement() : null);
|
||||
if (str2 == null) continue;
|
||||
if (sb == null) sb = new StringBuffer();
|
||||
sb.append(str2);
|
||||
sb.append("<br>");
|
||||
}
|
||||
else {
|
||||
sb = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (sb != null) return sb.toString();
|
||||
}
|
||||
return null;
|
||||
private String generateDocumentation(DocumentationProvider provider) {
|
||||
return provider.generateDoc(myExpressionList, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -285,6 +285,7 @@ javadoc.documentation.not.found.message=The documentation for this element is no
|
||||
javadoc.documentation.not.found.title=No Documentation
|
||||
javadoc.fetching.progress=Fetching Documentation...
|
||||
no.documentation.found=No documentation found.
|
||||
javadoc.constructor.candidates=<html>Candidates for new <b>{0}</b>() are:<br>{1}</html>
|
||||
javadoc.candidates=<html>Candidates for method call <b>{0}</b> are:<br><br>{1}</html>
|
||||
javadoc.candidates.not.found=<html>No candidates found for method call <b>{0}</b>.</html>
|
||||
declaration.navigation.title=Choose Declaration
|
||||
|
||||
Reference in New Issue
Block a user