WEB-65956 QuickDoc: ensure that <td> has a nested <p> or other block element.

GitOrigin-RevId: 88e43669da966071c3109972849d72fdf00c67ce
This commit is contained in:
Piotr Tomiak
2024-03-12 15:33:50 +00:00
committed by intellij-monorepo-bot
parent 5f519ec45c
commit edef8d4a82
6 changed files with 67 additions and 56 deletions
@@ -5,4 +5,4 @@
at least one element <tt>e</tt> such that
<tt>(o==null&nbsp;?&nbsp;e==null&nbsp;:&nbsp;o.<wbr>equals(e))</tt>.
</p></div><table class="sections"><tbody><tr><td valign="top" class="section"><p>Overrides:</p></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></p></td></tr><tr><td valign="top" class="section"><p>Params:</p></td><td valign="top"><code><span style="color:#000000;">o</span></code> – element whose presence in this list is to be tested </td></tr><tr><td valign="top" class="section"><p>Returns:</p></td><td valign="top"><p><tt>true</tt> if this list contains the specified element </p></td></tr><tr><td valign="top" class="section"><p>Throws:</p></td><td valign="top"><p><a href="psi_element://java.lang.ClassCastException"><code><span style="color:#0000ff;">ClassCastException</span></code></a> – if the type of the specified element is incompatible with this list (<a href="psi_element://java.util.Collection###optional-restrictions">optional</a>) </p><p><a href="psi_element://java.lang.NullPointerException"><code><span style="color:#0000ff;">NullPointerException</span></code></a> – 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>)</p></td></tr></tbody></table><div class="bottom"><icon src="AllIcons.Nodes.PpLibFolder" />&nbsp;&lt; java 1.7 &gt;</div></body></html>
</p></div><table class="sections"><tbody><tr><td valign="top" class="section"><p>Overrides:</p></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></p></td></tr><tr><td valign="top" class="section"><p>Params:</p></td><td valign="top"><p><code><span style="color:#000000;">o</span></code> – element whose presence in this list is to be tested </p></td></tr><tr><td valign="top" class="section"><p>Returns:</p></td><td valign="top"><p><tt>true</tt> if this list contains the specified element </p></td></tr><tr><td valign="top" class="section"><p>Throws:</p></td><td valign="top"><p><a href="psi_element://java.lang.ClassCastException"><code><span style="color:#0000ff;">ClassCastException</span></code></a> – if the type of the specified element is incompatible with this list (<a href="psi_element://java.util.Collection###optional-restrictions">optional</a>) </p><p><a href="psi_element://java.lang.NullPointerException"><code><span style="color:#0000ff;">NullPointerException</span></code></a> – 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>)</p></td></tr></tbody></table><div class="bottom"><icon src="AllIcons.Nodes.PpLibFolder" />&nbsp;&lt; java 1.7 &gt;</div></body></html>
@@ -6,4 +6,4 @@
contains at least one element <tt>e</tt> such that
<tt>(o==null&nbsp;?&nbsp;e==null&nbsp;:&nbsp;o.<wbr>equals(e))</tt>.
</p></td></tr><tr><td valign="top" class="section"><p>Overrides:</p></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><br><a href="psi_element://I#contains(java.lang.Object)"><code><span style="color:#000000;">contains</span></code></a> in interface <a href="psi_element://I"><code><span style="color:#000000;">I</span></code></a></p></td></tr><tr><td valign="top" class="section"><p>Params:</p></td><td valign="top"><code><span style="color:#000000;">o</span></code> – element whose presence in this collection is to be tested </td></tr><tr><td valign="top" class="section"><p>Returns:</p></td><td valign="top"><p><tt>true</tt> if this collection contains the specified element </p></td></tr><tr><td valign="top" class="section"><p>Throws:</p></td><td valign="top"><p><a href="psi_element://java.lang.NullPointerException"><code><span style="color:#0000ff;">NullPointerException</span></code></a> – before if the specified element is null and this collection does not permit null elements (<a href="psi_element://My###optional-restrictions">optional</a>) after</p><p><span style="color:#ff0000">IOException</span></p></td></tr></tbody></table></body></html>
</p></td></tr><tr><td valign="top" class="section"><p>Overrides:</p></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><br><a href="psi_element://I#contains(java.lang.Object)"><code><span style="color:#000000;">contains</span></code></a> in interface <a href="psi_element://I"><code><span style="color:#000000;">I</span></code></a></p></td></tr><tr><td valign="top" class="section"><p>Params:</p></td><td valign="top"><p><code><span style="color:#000000;">o</span></code> – element whose presence in this collection is to be tested </p></td></tr><tr><td valign="top" class="section"><p>Returns:</p></td><td valign="top"><p><tt>true</tt> if this collection contains the specified element </p></td></tr><tr><td valign="top" class="section"><p>Throws:</p></td><td valign="top"><p><a href="psi_element://java.lang.NullPointerException"><code><span style="color:#0000ff;">NullPointerException</span></code></a> – before if the specified element is null and this collection does not permit null elements (<a href="psi_element://My###optional-restrictions">optional</a>) after</p><p><span style="color:#ff0000">IOException</span></p></td></tr></tbody></table></body></html>
@@ -109,8 +109,8 @@ public class JavaDocRenderTest extends AbstractEditorTest {
* @author bar
*/
class C {}""", true);
verifyItem(15, 52,"<table class='sections'><p><tr><td valign='top' class='section'><p>Author:</td>" +
"<td valign='top'><p>foo, bar</td></table>");
verifyItem(15, 52,"<table class=\"sections\"><tbody><tr><td valign=\"top\" class=\"section\"><p>Author:</p></td>" +
"<td valign=\"top\"><p>foo, bar</p></td></tr></tbody></table>");
}
public void testDocumentStart() {
@@ -7,11 +7,14 @@ import com.intellij.openapi.module.UnknownModuleType
import com.intellij.ui.ColorUtil
import com.intellij.ui.components.JBHtmlPaneStyleConfiguration
import com.intellij.ui.scale.JBUIScale.scale
import com.intellij.util.SmartList
import com.intellij.util.ui.JBUI
import com.intellij.util.ui.StyleSheetUtil
import com.intellij.util.ui.UIUtil
import org.intellij.lang.annotations.Language
import org.jetbrains.annotations.ApiStatus
import org.jsoup.nodes.*
import java.util.*
import java.util.function.Function
import javax.swing.Icon
import javax.swing.text.html.StyleSheet
@@ -98,4 +101,49 @@ object DocumentationHtmlUtil {
""".trimIndent()
return StyleSheetUtil.loadStyleSheet(result)
}
@JvmStatic
internal fun addExternalLinkIcons(document: Document) {
document.select("a").forEach { a ->
if (a.attribute("href")?.value?.startsWith("http") == true) {
Element("icon").attr("src", "AllIcons.Ide.External_link_arrow")
.appendTo(a)
}
}
}
@JvmStatic
internal fun addParagraphsIfNeeded(document: Document, selector: String) {
document.select(selector).forEach { element ->
var child = element.firstChild()
val toWrap = SmartList<Node>()
while (child != null && !isBlockElement(child)) {
if (child !is Comment) {
toWrap.add(child);
}
child = child.nextSibling();
}
if (!toWrap.isEmpty() && !toWrap.all { n -> n is TextNode && n.isBlank }) {
val para = Element("p");
para.insertChildren(0, toWrap);
element.insertChildren(0, para);
}
}
}
private fun isBlockElement(node: Node): Boolean {
if (node is Element) {
val tagName = node.tagName().lowercase(Locale.US)
return tagName == "p"
|| tagName == "div"
|| tagName == "pre"
|| tagName == "table"
|| tagName == "blockquote"
|| tagName == "ol"
|| tagName == "ul"
|| tagName == "dl"
|| (tagName.startsWith("h") && tagName.length == 2 && Character.isDigit(tagName[1]))
}
return false
}
}
@@ -84,10 +84,7 @@ import org.jetbrains.annotations.*;
import org.jetbrains.concurrency.CancellablePromise;
import org.jetbrains.concurrency.Promises;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Comment;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jsoup.nodes.TextNode;
import javax.swing.*;
import java.awt.*;
@@ -107,7 +104,6 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.regex.Pattern;
import static com.intellij.lang.documentation.DocumentationMarkup.*;
@@ -1948,14 +1944,14 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp
document.body().append(getBottom().child(links).toString());
}
document.select("." + CLASS_DEFINITION + ", ." + CLASS_CONTENT).forEach(
document.select("." + CLASS_DEFINITION + ", ." + CLASS_CONTENT + ", ." + CLASS_SECTIONS).forEach(
div -> {
var nextSibling = div.nextElementSibling();
if (nextSibling == null) {
return;
}
if (nextSibling.hasClass(CLASS_DEFINITION)
|| nextSibling.hasClass(CLASS_CONTENT)
|| (nextSibling.hasClass(CLASS_CONTENT) && !div.hasClass(CLASS_SECTIONS))
|| (div.hasClass(CLASS_DEFINITION)
&& (
nextSibling.hasClass(CLASS_SECTIONS)
@@ -1966,44 +1962,11 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp
}
}
);
document.select("." + CLASS_CONTENT).forEach(
div -> {
var child = div.firstChild();
var toWrap = new SmartList<Node>();
while (child != null && !isBlockElement(child)) {
if (!(child instanceof Comment)) {
toWrap.add(child);
}
child = child.nextSibling();
}
if (!toWrap.isEmpty()
&& !ContainerUtil.all(toWrap, n -> n instanceof TextNode textNode && textNode.isBlank())
) {
var para = new Element("p");
para.insertChildren(0, toWrap);
div.insertChildren(0, para);
}
}
);
DocumentationHtmlUtil.addParagraphsIfNeeded$intellij_platform_lang_impl(
document, "." + CLASS_CONTENT + ", table." + CLASS_SECTIONS + " td[valign=top]");
DocumentationHtmlUtil.addExternalLinkIcons$intellij_platform_lang_impl(document);
document.outputSettings().prettyPrint(false);
return addExternalLinksIcon(document.html());
}
private static boolean isBlockElement(Node node) {
if (node instanceof Element element) {
var tagName = element.tagName();
return tagName.equals("p")
|| tagName.equals("div")
|| tagName.equals("pre")
|| tagName.equals("table")
|| tagName.equals("blockquote")
|| tagName.equals("ol")
|| tagName.equals("ul")
|| tagName.equals("dl")
|| (tagName.startsWith("h") && tagName.length() == 2 && Character.isDigit(tagName.charAt(1)));
}
return false;
return document.html();
}
private static @NlsSafe @NotNull String replaceIgnoreQuotesType(@NotNull String text,
@@ -2136,11 +2099,4 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp
return HtmlChunk.div().setClass(CLASS_BOTTOM);
}
private static final Pattern EXTERNAL_LINK_PATTERN = Pattern.compile("(<a\\s*href=[\"']http[^>]*>)([^>]*)(</a>)");
private static final @NlsSafe String EXTERNAL_LINK_REPLACEMENT = "$1$2<icon src='AllIcons.Ide.External_link_arrow'>$3";
@Contract(pure = true)
public static String addExternalLinksIcon(String text) {
return EXTERNAL_LINK_PATTERN.matcher(text).replaceAll(EXTERNAL_LINK_REPLACEMENT);
}
}
@@ -3,7 +3,7 @@ package com.intellij.codeInsight.documentation.render;
import com.intellij.codeHighlighting.*;
import com.intellij.codeInsight.CodeInsightBundle;
import com.intellij.codeInsight.documentation.DocumentationManager;
import com.intellij.codeInsight.documentation.DocumentationHtmlUtil;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.progress.ProgressIndicator;
@@ -20,12 +20,14 @@ import com.intellij.util.text.CharArrayUtil;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jsoup.Jsoup;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import static com.intellij.codeInsight.documentation.render.InlineDocumentationImplKt.inlineDocumentationItems;
import static com.intellij.lang.documentation.DocumentationMarkup.CLASS_SECTIONS;
public final class DocRenderPassFactory implements TextEditorHighlightingPassFactoryRegistrar, TextEditorHighlightingPassFactory, DumbAware {
private static final Key<Long> MODIFICATION_STAMP = Key.create("doc.render.modification.stamp");
@@ -113,7 +115,12 @@ public final class DocRenderPassFactory implements TextEditorHighlightingPassFac
}
private static String preProcess(String text) {
return DocumentationManager.addExternalLinksIcon(text);
var document = Jsoup.parse(text);
DocumentationHtmlUtil.addParagraphsIfNeeded$intellij_platform_lang_impl(
document, "table." + CLASS_SECTIONS + " td[valign=top]");
DocumentationHtmlUtil.addExternalLinkIcons$intellij_platform_lang_impl(document);
document.outputSettings().prettyPrint(false);
return document.html();
}
public static void applyItemsToRender(@NotNull Editor editor,