diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ResponseNodeRenderer.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ResponseNodeRenderer.java index 3d060b11..96b9de1a 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ResponseNodeRenderer.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ResponseNodeRenderer.java @@ -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(); diff --git a/src/main/kotlin/ee/carlrobert/codegpt/util/MarkdownUtil.kt b/src/main/kotlin/ee/carlrobert/codegpt/util/MarkdownUtil.kt index 76cc66d0..ff27e285 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/util/MarkdownUtil.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/util/MarkdownUtil.kt @@ -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, "
") val document = Parser.builder(options).build().parse(message) diff --git a/src/test/kotlin/ee/carlrobert/codegpt/util/MarkdownUtilTest.kt b/src/test/kotlin/ee/carlrobert/codegpt/util/MarkdownUtilTest.kt index f1b60c95..7a4c1647 100644 --- a/src/test/kotlin/ee/carlrobert/codegpt/util/MarkdownUtilTest.kt +++ b/src/test/kotlin/ee/carlrobert/codegpt/util/MarkdownUtilTest.kt @@ -5,6 +5,40 @@ import org.junit.Test class MarkdownUtilTest { + @Test + fun shouldRenderTableCorrectly() { + val markdown = """ + thinking content + 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("