[documentation] PSI-independent HtmlChunk-based DocumentationManager#decorate

GitOrigin-RevId: 557af2aa1558b83116da6e94e31203a1e72f758c
This commit is contained in:
Daniil Ovchinnikov
2021-08-27 12:36:25 +02:00
committed by intellij-monorepo-bot
parent 0ef4feb02b
commit 52898f753c
9 changed files with 45 additions and 31 deletions

View File

@@ -26,4 +26,4 @@
</ul>
</div>
</div>
</div><div class='bottom'><icon src='AllIcons.Nodes.PpLibFolder'>&nbsp;myLib</div><div class='bottom'><a href="placeholder">`genericMethod(Class&lt;?&gt;)` on localhost<icon src='AllIcons.Ide.External_link_arrow'></a>
</div><div class="bottom"><icon src="AllIcons.Nodes.PpLibFolder"/>&nbsp;myLib</div><div class="bottom"><a href="placeholder">`genericMethod(Class&lt;?&gt;)` on localhost<icon src='AllIcons.Ide.External_link_arrow'></a></div>

View File

@@ -19,4 +19,4 @@
<div class="block"><a href="psi_element://com.jetbrains.LinkBetweenMethods#m2()"><code>m2()</code></a></div>
</li>
</ul>
</div><div class='bottom'><icon src='AllIcons.Nodes.PpLibFolder'>&nbsp;myLib</div><div class='bottom'><a href="placeholder">`m1()` on localhost<icon src='AllIcons.Ide.External_link_arrow'></a>
</div><div class="bottom"><icon src="AllIcons.Nodes.PpLibFolder"/>&nbsp;myLib</div><div class="bottom"><a href="placeholder">`m1()` on localhost<icon src='AllIcons.Ide.External_link_arrow'></a></div>

View File

@@ -24,4 +24,4 @@
</ul>
</div>
</div>
</div><div class='bottom'><icon src='AllIcons.Nodes.PpLibFolder'>&nbsp;myLib</div><div class='bottom'><a href="placeholder">`SimpleInterface` on localhost<icon src='AllIcons.Ide.External_link_arrow'></a>
</div><div class="bottom"><icon src="AllIcons.Nodes.PpLibFolder"/>&nbsp;myLib</div><div class="bottom"><a href="placeholder">`SimpleInterface` on localhost<icon src='AllIcons.Ide.External_link_arrow'></a></div>

View File

@@ -32,4 +32,4 @@ extends java.lang.Object</pre>
</li>
</ul>
</div>
</div><div class='bottom'><icon src='AllIcons.Nodes.PpLibFolder'>&nbsp;myLib</div><div class='bottom'><a href="placeholder">`ClassWithRefLink` on localhost<icon src='AllIcons.Ide.External_link_arrow'></a>
</div><div class="bottom"><icon src="AllIcons.Nodes.PpLibFolder"/>&nbsp;myLib</div><div class="bottom"><a href="placeholder">`ClassWithRefLink` on localhost<icon src='AllIcons.Ide.External_link_arrow'></a></div>

View File

@@ -26,4 +26,4 @@
</ul>
</div>
</div>
</div><div class='bottom'><icon src='AllIcons.Nodes.PpLibFolder'>&nbsp;myLib</div><div class='bottom'><a href="placeholder">`param()` on localhost<icon src='AllIcons.Ide.External_link_arrow'></a>
</div><div class="bottom"><icon src="AllIcons.Nodes.PpLibFolder"/>&nbsp;myLib</div><div class="bottom"><a href="placeholder">`param()` on localhost<icon src='AllIcons.Ide.External_link_arrow'></a></div>

View File

@@ -5,4 +5,4 @@
at least one element <tt>e</tt> such that
<tt>(o==null&nbsp;?&nbsp;e==null&nbsp;:&nbsp;o.equals(e))</tt>.
</div><table class='sections'><tr><td valign='top' class='section'><p>Overrides:</td><td valign='top'><p><a href="psi_element://java.util.Collection#contains(java.lang.Object)"><code><span style="color:#000000;">contains</span></code></a> in interface <a href="psi_element://java.util.Collection"><code><span style="color:#000000;">Collection</span></code></a></td><tr><td valign='top' class='section'><p>Params:</td><td valign='top'><span style="">o</span> &ndash; element whose presence in this list is to be tested </td><tr><td valign='top' class='section'><p>Returns:</td><td valign='top'><p><tt>true</tt> if this list contains the specified element </td><tr><td valign='top' class='section'><p>Throws:</td><td valign='top'><p><a href="psi_element://java.lang.ClassCastException"><code><span style="color:#000000;">ClassCastException</span></code></a> &ndash; if the type of the specified element is incompatible with this list (<a href="psi_element://java.util.Collection###optional-restrictions">optional</a>) <p><a href="psi_element://java.lang.NullPointerException"><code><span style="color:#000000;">NullPointerException</span></code></a> &ndash; if the specified element is null and this list does not permit null elements (<a href="psi_element://java.util.Collection###optional-restrictions">optional</a>)</td><tr><td valign='top' class='section'><p>External<br> annotations:</td><td valign='top'><p><span style="color:#808000;">@</span><a href="psi_element://org.jetbrains.annotations.Contract"><span style="color:#808000;">org.jetbrains.annotations.Contract</span></a><span style="">(</span><span style="">pure</span><span style=""> = </span><span style="color:#000080;font-weight:bold;">true</span><span style="">)</span></td></table><div class='bottom'><icon src='AllIcons.Nodes.PpLibFolder'>&nbsp;&lt; java 1.7 ></div>
</div><table class='sections'><tr><td valign='top' class='section'><p>Overrides:</td><td valign='top'><p><a href="psi_element://java.util.Collection#contains(java.lang.Object)"><code><span style="color:#000000;">contains</span></code></a> in interface <a href="psi_element://java.util.Collection"><code><span style="color:#000000;">Collection</span></code></a></td><tr><td valign='top' class='section'><p>Params:</td><td valign='top'><span style="">o</span> &ndash; element whose presence in this list is to be tested </td><tr><td valign='top' class='section'><p>Returns:</td><td valign='top'><p><tt>true</tt> if this list contains the specified element </td><tr><td valign='top' class='section'><p>Throws:</td><td valign='top'><p><a href="psi_element://java.lang.ClassCastException"><code><span style="color:#000000;">ClassCastException</span></code></a> &ndash; if the type of the specified element is incompatible with this list (<a href="psi_element://java.util.Collection###optional-restrictions">optional</a>) <p><a href="psi_element://java.lang.NullPointerException"><code><span style="color:#000000;">NullPointerException</span></code></a> &ndash; if the specified element is null and this list does not permit null elements (<a href="psi_element://java.util.Collection###optional-restrictions">optional</a>)</td><tr><td valign='top' class='section'><p>External<br> annotations:</td><td valign='top'><p><span style="color:#808000;">@</span><a href="psi_element://org.jetbrains.annotations.Contract"><span style="color:#808000;">org.jetbrains.annotations.Contract</span></a><span style="">(</span><span style="">pure</span><span style=""> = </span><span style="color:#000080;font-weight:bold;">true</span><span style="">)</span></td></table><div class="bottom"><icon src="AllIcons.Nodes.PpLibFolder"/>&nbsp;&lt; java 1.7 &gt;</div>

View File

@@ -270,7 +270,7 @@ class Bar {
}
// Here we check that the covering module (SDK in this case) is rendered in decorated info
assert component.decoratedText.contains("<div class='bottom'><icon src='AllIcons.Nodes.PpLibFolder'>&nbsp;&lt; java 1.7 ></div>")
assert component.decoratedText.contains('<div class="bottom"><icon src="AllIcons.Nodes.PpLibFolder"/>&nbsp;&lt; java 1.7 &gt;</div>')
}

View File

@@ -28,4 +28,5 @@ public interface DocumentationMarkup {
HtmlChunk.Element DEFINITION_ELEMENT = HtmlChunk.div().setClass("definition");
HtmlChunk.Element GRAYED_ELEMENT = HtmlChunk.span().setClass("grayed");
HtmlChunk.Element CENTERED_ELEMENT = HtmlChunk.p().setClass("centered");
HtmlChunk.Element EXTERNAL_LINK_ICON = HtmlChunk.tag("icon").attr("src", "AllIcons.Ide.External_link_arrow");
}

View File

@@ -43,6 +43,7 @@ import com.intellij.openapi.roots.ui.configuration.ProjectSettingsService;
import com.intellij.openapi.ui.popup.JBPopup;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.util.*;
import com.intellij.openapi.util.text.HtmlBuilder;
import com.intellij.openapi.util.text.HtmlChunk;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vcs.FileStatus;
@@ -1765,11 +1766,16 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp
@NlsSafe @Nullable String externalUrl,
@Nullable DocumentationProvider provider
) {
return decorate(text, getLocationText(element), getExternalText(element, externalUrl, provider));
}
@Internal
@Contract(pure = true)
public static @Nls String decorate(@Nls @NotNull String text, @Nullable HtmlChunk location, @Nullable HtmlChunk links) {
text = StringUtil.replaceIgnoreCase(text, "</html>", "");
text = StringUtil.replaceIgnoreCase(text, "</body>", "");
text = StringUtil.replaceIgnoreCase(text, SECTIONS_START + SECTIONS_END, "");
//noinspection HardCodedStringLiteral
text = StringUtil.replaceIgnoreCase(text, SECTIONS_START + "<p>" + SECTIONS_END, "");
text = StringUtil.replaceIgnoreCase(text, SECTIONS_START + "<p>" + SECTIONS_END, ""); //NON-NLS
boolean hasContent = text.contains(CONTENT_START);
if (!hasContent) {
if (!text.contains(DEFINITION_START)) {
@@ -1793,14 +1799,11 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp
if (!text.contains(DEFINITION_START)) {
text = text.replace("class='content'", "class='content-only'");
}
String location = getLocationText(element);
if (location != null) {
//noinspection HardCodedStringLiteral
text = text + getBottom(hasContent) + location + "</div>";
text += getBottom(hasContent).child(location);
}
String links = getExternalText(element, externalUrl, provider);
if (links != null) {
text = text + getBottom(location != null) + links;
text += getBottom(location != null).child(links);
}
//workaround for Swing html renderer not removing empty paragraphs before non-inline tags
text = text.replaceAll("<p>\\s*(<(?:[uo]l|h\\d|p))", "$1");
@@ -1808,7 +1811,9 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp
return text;
}
private @Nls @Nullable String getExternalText(
@RequiresReadLock
@RequiresBackgroundThread
private @Nullable HtmlChunk getExternalText(
@Nullable PsiElement element,
@NlsSafe @Nullable String externalUrl,
@Nullable DocumentationProvider provider
@@ -1825,34 +1830,33 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp
List<String> urls = provider.getUrlFor(element, originalElement);
if (urls != null) {
boolean hasBadUrl = false;
@Nls StringBuilder result = new StringBuilder();
var result = new HtmlBuilder();
for (String url : urls) {
String link = getLink(title, url);
HtmlChunk link = getLink(title, url);
if (link == null) {
hasBadUrl = true;
break;
}
if (result.length() > 0) result.append("<p>");
if (!result.isEmpty()) result.append(HtmlChunk.p());
result.append(link);
}
if (!hasBadUrl) return result.toString();
if (!hasBadUrl) return result.toFragment();
}
else {
return null;
}
}
else {
String link = getLink(title, externalUrl);
HtmlChunk link = getLink(title, externalUrl);
if (link != null) return link;
}
String linkText = CodeInsightBundle.message("html.external.documentation.component.header", title, title == null ? 0 : 1);
return HtmlChunk.link("external_doc", linkText)
.child(HtmlChunk.tag("icon").attr("src", "AllIcons.Ide.External_link_arrow")).toString();
return HtmlChunk.link("external_doc", linkText).child(EXTERNAL_LINK_ICON);
}
private static @Nls String getLink(@Nls String title, @NlsSafe String url) {
@Internal
public static @Nullable HtmlChunk getLink(@Nls String title, @NlsSafe String url) {
String hostname = getHostname(url);
if (hostname == null) {
return null;
@@ -1864,7 +1868,7 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp
else {
linkText = CodeInsightBundle.message("link.text.element.documentation.on.url", title, hostname);
}
return HtmlChunk.link(url, linkText).toString();
return HtmlChunk.link(url, linkText);
}
static boolean shouldShowExternalDocumentationLink(DocumentationProvider provider,
@@ -1904,9 +1908,8 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp
return -1;
}
@NotNull
private static String getBottom(boolean hasContent) {
return "<div class='" + (hasContent ? "bottom" : "bottom-no-content") + "'>";
private static @NotNull HtmlChunk.Element getBottom(boolean hasContent) {
return HtmlChunk.div().setClass(hasContent ? "bottom" : "bottom-no-content");
}
private static final Pattern EXTERNAL_LINK_PATTERN = Pattern.compile("(<a\\s*href=[\"']http[^>]*>)([^>]*)(</a>)");
@@ -1917,7 +1920,9 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp
return EXTERNAL_LINK_PATTERN.matcher(text).replaceAll(EXTERNAL_LINK_REPLACEMENT);
}
private static @NlsSafe String getLocationText(@Nullable PsiElement element) {
@RequiresReadLock
@RequiresBackgroundThread
private static @Nullable HtmlChunk getLocationText(@Nullable PsiElement element) {
if (element != null) {
PsiFile file = element.getContainingFile();
VirtualFile vfile = file == null ? null : file.getVirtualFile();
@@ -1934,13 +1939,21 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp
if (module != null && !ModuleType.isInternal(module)) {
if (ModuleManager.getInstance(element.getProject()).getModules().length == 1) return null;
return "<icon src='" + ModuleType.get(module).getId() + "'>&nbsp;" + module.getName().replace("<", "&lt;");
return HtmlChunk.fragment(
HtmlChunk.tag("icon").attr("src", ModuleType.get(module).getId()),
HtmlChunk.nbsp(),
HtmlChunk.text(module.getName())
);
}
else {
List<OrderEntry> entries = fileIndex.getOrderEntriesForFile(vfile);
for (OrderEntry order : entries) {
if (order instanceof LibraryOrderEntry || order instanceof JdkOrderEntry) {
return "<icon src='AllIcons.Nodes.PpLibFolder" + "'>&nbsp;" + order.getPresentableName().replace("<", "&lt;");
return HtmlChunk.fragment(
HtmlChunk.tag("icon").attr("src", "AllIcons.Nodes.PpLibFolder"),
HtmlChunk.nbsp(),
HtmlChunk.text(order.getPresentableName())
);
}
}
}