IDEA-138063 Support HTML links with fragment references in quick doc

This commit is contained in:
Dmitry Batrak
2015-05-19 13:26:06 +03:00
parent dc2b0581e2
commit f7ecb8a5f7
7 changed files with 86 additions and 10 deletions
@@ -252,10 +252,17 @@ public class JavaDocInfoGenerator {
protected String convertReference(@NonNls String href) {
final String originalReference = href;
String fragment = null;
int hashPosition = href.indexOf('#');
if (hashPosition >= 0) {
fragment = href.substring(hashPosition + 1);
href = href.substring(0, hashPosition);
}
if (href.isEmpty()) {
PsiElement containingClass = myElement instanceof PsiMember ? ((PsiMember)myElement).getContainingClass() : null;
PsiElement rootElement = containingClass == null ? myElement : containingClass;
return createLinkWithRef(rootElement, fragment);
}
if (!href.toLowerCase().endsWith(".htm") && !href.toLowerCase().endsWith(".html")) {
return originalReference;
}
@@ -286,7 +293,12 @@ public class JavaDocInfoGenerator {
PsiClass target = JavaPsiFacade.getInstance(myProject).findClass(qualifiedName, myElement.getResolveScope());
if (target == null) return originalReference;
return DocumentationManagerProtocol.PSI_ELEMENT_PROTOCOL + JavaDocUtil.getReferenceText(myProject, target);
return createLinkWithRef(target, fragment);
}
private String createLinkWithRef(PsiElement psiElement, String ref) {
return DocumentationManagerProtocol.PSI_ELEMENT_PROTOCOL + JavaDocUtil.getReferenceText(myProject, psiElement) +
(ref == null ? "" : DocumentationManagerProtocol.PSI_ELEMENT_PROTOCOL_REF_SEPARATOR + ref);
}
public boolean generateDocInfoCore (final StringBuilder buffer, final boolean generatePrologueAndEpilogue) {
@@ -0,0 +1,5 @@
<html><head><base href="placeholder"> <style type="text/css"> #error { background-color: #eeeeee; margin-bottom: 10px; } p { margin: 5px 0; } </style></head><body><PRE>class <b>Test</b>
extends <a href="psi_element://java.lang.Object"><code>Object</code></a></PRE>
<a name="section1">section one</a>
text
<a href="psi_element://Test###section1">link up</a></body></html>
@@ -0,0 +1,6 @@
/**
* <a name="section1">section one</a>
* text
* <a href="#section1">link up</a>
*/
class Test {}
@@ -254,6 +254,10 @@ public class JavaDocInfoGeneratorTest extends CodeInsightTestCase {
assertEquals(StringUtil.convertLineSeparators(new String(htmlFile.getVirtualFile().contentsToByteArray()).trim()),
replaceEnvironmentDependentContent(doc));
}
public void testHtmlLinkWithRef() throws Exception {
verifyJavaDoc(getTestClass());
}
@Override
protected String getTestDataPath() {
@@ -27,6 +27,12 @@ public interface DocumentationManagerProtocol {
* @see DocumentationManagerUtil
*/
@NonNls String PSI_ELEMENT_PROTOCOL = "psi_element://";
/**
* Separator between PSI element link and a reference to specific text fragment, which should be scrolled to on navigation. Can be used
* with {@link #PSI_ELEMENT_PROTOCOL} links, full link should look like {@code psi_element://link###ref}.
*/
@NonNls String PSI_ELEMENT_PROTOCOL_REF_SEPARATOR = "###";
/**
* @deprecated Link with such prefix will open documentation for current element in browser, regardless of full link content. Http links
@@ -524,6 +524,10 @@ public class DocumentationComponent extends JPanel implements Disposable, DataPr
}
public void setData(PsiElement _element, String text, final boolean clearHistory, String effectiveExternalUrl) {
setData(_element, text, clearHistory, effectiveExternalUrl, null);
}
public void setData(PsiElement _element, String text, final boolean clearHistory, String effectiveExternalUrl, String ref) {
myEffectiveExternalUrl = effectiveExternalUrl;
if (myElement != null) {
myBackStack.push(saveContext());
@@ -540,16 +544,16 @@ public class DocumentationComponent extends JPanel implements Disposable, DataPr
myIsEmpty = false;
updateControlState();
setDataInternal(element, text, new Rectangle(0, 0));
setDataInternal(element, text, new Rectangle(0, 0), ref);
if (clearHistory) clearHistory();
}
private void setDataInternal(SmartPsiElementPointer element, String text, final Rectangle viewRect) {
setDataInternal(element, text, viewRect, false);
private void setDataInternal(SmartPsiElementPointer element, String text, final Rectangle viewRect, String ref) {
setDataInternal(element, text, viewRect, ref, false);
}
private void setDataInternal(SmartPsiElementPointer element, String text, final Rectangle viewRect, boolean skip) {
private void setDataInternal(SmartPsiElementPointer element, String text, final Rectangle viewRect, final String ref, boolean skip) {
setElement(element);
myEditorPane.setText(text);
@@ -568,7 +572,10 @@ public class DocumentationComponent extends JPanel implements Disposable, DataPr
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
myEditorPane.scrollRectToVisible(viewRect);
myEditorPane.scrollRectToVisible(viewRect); // if ref is defined but is not found in document, this provides a default location
if (ref != null) {
myEditorPane.scrollToReference(ref);
}
}
});
}
@@ -618,7 +625,7 @@ public class DocumentationComponent extends JPanel implements Disposable, DataPr
}
private void restoreContext(Context context) {
setDataInternal(context.element, context.text, context.viewRect);
setDataInternal(context.element, context.text, context.viewRect, null);
if (myNavigateCallback != null) {
final PsiElement element = context.element.getElement();
if (element != null) {
@@ -641,6 +641,10 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp
return new DefaultDocumentationCollector(element, originalElement);
}
private DocumentationCollector getDefaultCollector(@NotNull final PsiElement element, @Nullable final PsiElement originalElement, String ref) {
return new DefaultDocumentationCollector(element, originalElement, ref);
}
@Nullable
public JBPopup getDocInfoHint() {
if (myDocInfoHintRef == null) return null;
@@ -748,7 +752,7 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp
component.setText(component.getText(), element, true, clearHistory);
}
else {
component.setData(element, documentationText, clearHistory, provider.getEffectiveExternalUrl());
component.setData(element, documentationText, clearHistory, provider.getEffectiveExternalUrl(), provider.getRef());
}
final AbstractPopup jbPopup = (AbstractPopup)getDocInfoHint();
@@ -854,7 +858,13 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp
}
}
else if (url.startsWith(DocumentationManagerProtocol.PSI_ELEMENT_PROTOCOL)) {
final String refText = url.substring(DocumentationManagerProtocol.PSI_ELEMENT_PROTOCOL.length());
String refText = url.substring(DocumentationManagerProtocol.PSI_ELEMENT_PROTOCOL.length());
int separatorPos = refText.lastIndexOf(DocumentationManagerProtocol.PSI_ELEMENT_PROTOCOL_REF_SEPARATOR);
String ref = null;
if (separatorPos >= 0) {
ref = refText.substring(separatorPos + DocumentationManagerProtocol.PSI_ELEMENT_PROTOCOL_REF_SEPARATOR.length());
refText = refText.substring(0, separatorPos);
}
DocumentationProvider provider = getProviderFromElement(psiElement);
PsiElement targetElement = provider.getDocumentationElementForLink(manager, refText, psiElement);
if (targetElement == null) {
@@ -877,7 +887,7 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp
}
}
if (targetElement != null) {
fetchDocInfo(getDefaultCollector(targetElement, null), component);
fetchDocInfo(getDefaultCollector(targetElement, null, ref), component);
}
}
else {
@@ -905,6 +915,12 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp
public String getEffectiveExternalUrl() {
return url;
}
@Nullable
@Override
public String getRef() {
return null;
}
}, component);
processed = true;
}
@@ -958,6 +974,12 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp
public String getEffectiveExternalUrl() {
return url;
}
@Nullable
@Override
public String getRef() {
return null;
}
}, component);
}
}
@@ -1049,18 +1071,26 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp
PsiElement getElement();
@Nullable
String getEffectiveExternalUrl();
@Nullable
String getRef();
}
private class DefaultDocumentationCollector implements DocumentationCollector {
private final PsiElement myElement;
private final PsiElement myOriginalElement;
private final String myRef;
private String myEffectiveUrl;
private DefaultDocumentationCollector(PsiElement element, PsiElement originalElement) {
this(element, originalElement, null);
}
private DefaultDocumentationCollector(PsiElement element, PsiElement originalElement, String ref) {
myElement = element;
myOriginalElement = originalElement;
myRef = ref;
}
@Override
@@ -1122,5 +1152,11 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp
public String getEffectiveExternalUrl() {
return myEffectiveUrl;
}
@Nullable
@Override
public String getRef() {
return myRef;
}
}
}