feat: support markdown tables (closes #1186)

This commit is contained in:
Carl-Robert Linnupuu 2026-02-26 12:25:19 +00:00
parent b501ffad73
commit fe02380f5c
3 changed files with 80 additions and 1 deletions

View file

@ -9,6 +9,10 @@ import com.vladsch.flexmark.ast.Heading;
import com.vladsch.flexmark.ast.Link;
import com.vladsch.flexmark.ast.OrderedListItem;
import com.vladsch.flexmark.ast.Paragraph;
import com.vladsch.flexmark.ext.tables.TableBlock;
import com.vladsch.flexmark.ext.tables.TableCell;
import com.vladsch.flexmark.ext.tables.TableHead;
import com.vladsch.flexmark.ext.tables.TableRow;
import com.vladsch.flexmark.html.HtmlWriter;
import com.vladsch.flexmark.html.renderer.NodeRenderer;
import com.vladsch.flexmark.html.renderer.NodeRendererContext;
@ -28,10 +32,49 @@ public class ResponseNodeRenderer implements NodeRenderer {
new NodeRenderingHandler<>(CodeBlock.class, this::renderCodeBlock),
new NodeRenderingHandler<>(BulletListItem.class, this::renderBulletListItem),
new NodeRenderingHandler<>(Heading.class, this::renderHeading),
new NodeRenderingHandler<>(OrderedListItem.class, this::renderOrderedListItem)
new NodeRenderingHandler<>(OrderedListItem.class, this::renderOrderedListItem),
new NodeRenderingHandler<>(TableBlock.class, this::renderTable),
new NodeRenderingHandler<>(TableRow.class, this::renderTableRow),
new NodeRenderingHandler<>(TableCell.class, this::renderTableCell)
);
}
private void renderTable(TableBlock node, NodeRendererContext context, HtmlWriter html) {
var borderColor = ColorUtil.toHex(new JBColor(0xD0D0D0, 0x3C3F47));
html.attr("style",
"border-collapse: collapse; width: 100%; margin: 8px 0; border-top: 1px solid "
+ borderColor);
context.delegateRender();
}
private void renderTableRow(TableRow node, NodeRendererContext context, HtmlWriter html) {
html.attr("style",
"border-bottom: 1px solid " + ColorUtil.toHex(new JBColor(0xE3E3E3, 0x2D2F35)) + ";");
context.delegateRender();
}
private void renderTableCell(TableCell node, NodeRendererContext context, HtmlWriter html) {
TableRow row = (TableRow) node.getParent();
var isHeaderCell = row != null && row.getParent() instanceof TableHead;
var tag = isHeaderCell ? "th" : "td";
var styleBuilder = new StringBuilder();
styleBuilder.append("padding: 8px 12px; text-align: left; vertical-align: middle;");
if (isHeaderCell) {
var bgColor = ColorUtil.toHex(new JBColor(0xF2F3F5, 0x3A3D41));
styleBuilder.append(" font-weight: 600; background-color: ").append(bgColor).append("; color: white; min-width: 200px;");
}
html.attr("style", styleBuilder.toString().trim());
if (isHeaderCell) {
html.attr("scope", "col");
}
html.withAttr().tag(tag);
context.renderChildren(node);
html.tag("/" + tag);
}
private void renderCodeBlock(CodeBlock node, NodeRendererContext context, HtmlWriter html) {
html.attr("style", "white-space: pre-wrap;");
context.delegateRender();

View file

@ -1,5 +1,6 @@
package ee.carlrobert.codegpt.util
import com.vladsch.flexmark.ext.tables.TablesExtension
import com.vladsch.flexmark.html.HtmlRenderer
import com.vladsch.flexmark.parser.Parser
import com.vladsch.flexmark.util.data.MutableDataSet
@ -37,6 +38,7 @@ object MarkdownUtil {
@JvmStatic
fun convertMdToHtml(message: String): String {
val options = MutableDataSet()
options.set(Parser.EXTENSIONS, listOf(TablesExtension.create()))
options.set(HtmlRenderer.SOFT_BREAK, "<br/>")
val document = Parser.builder(options).build().parse(message)

View file

@ -5,6 +5,40 @@ import org.junit.Test
class MarkdownUtilTest {
@Test
fun shouldRenderTableCorrectly() {
val markdown = """
<think>thinking content</think>
Here's a basic fruits table:
| Fruit | Color | Price ($/kg) | Season | Calories (per 100g) |
|-------|-------|--------------|--------|---------------------|
| Apple | Red | 3.50 | Fall | 52 |
| Banana | Yellow | 2.80 | Year-round | 89 |
| Orange | Orange | 4.20 | Winter | 47 |
| Strawberry | Red | 6.00 | Spring | 32 |
| Grape | Purple | 5.50 | Summer | 69 |
| Mango | Orange | 7.00 | Summer | 60 |
| Watermelon | Green | 1.90 | Summer | 30 |
| Kiwi | Brown | 4.80 | Winter | 61 |
| Pineapple | Yellow | 5.00 | Year-round | 50 |
| Blueberry | Blue | 8.50 | Summer | 57 |
""".trimIndent()
val html = MarkdownUtil.convertMdToHtml(markdown)
assertThat(html).contains("thead")
assertThat(html).contains("/thead")
assertThat(html).contains("tbody")
assertThat(html).contains("/tbody")
assertThat(html).contains("th")
assertThat(html).contains("Fruit")
assertThat(html).contains("td")
assertThat(html).contains("Apple")
val tableCount = html.split("<table").size - 1
assertThat(tableCount).isEqualTo(1)
}
@Test
fun shouldExtractMarkdownCodeBlocks() {
val testInput = """