diff --git a/ide-plugins/src/main/kotlin/com/picocode/PicoCodeToolWindowContent.kt b/ide-plugins/src/main/kotlin/com/picocode/PicoCodeToolWindowContent.kt index 94bb050..ced90a0 100644 --- a/ide-plugins/src/main/kotlin/com/picocode/PicoCodeToolWindowContent.kt +++ b/ide-plugins/src/main/kotlin/com/picocode/PicoCodeToolWindowContent.kt @@ -4,16 +4,49 @@ import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.project.Project import com.intellij.ui.components.JBScrollPane import com.intellij.ui.components.JBTextArea +import com.intellij.ui.JBColor import java.awt.BorderLayout import java.awt.Dimension import java.awt.FlowLayout import javax.swing.* +import javax.swing.text.html.HTMLEditorKit import java.net.HttpURLConnection import java.net.URL import com.google.gson.Gson import com.google.gson.JsonObject import com.google.gson.JsonArray +/** + * Custom JEditorPane that tracks viewport width for proper HTML wrapping + */ +class WrappingEditorPane : JEditorPane() { + override fun getScrollableTracksViewportWidth(): Boolean = true + + override fun getPreferredSize(): Dimension { + // Let the parent determine the width, we only care about height + val preferredSize = super.getPreferredSize() + + // If we're in a scroll pane, use the viewport width + val parent = parent + if (parent is JViewport) { + val viewportWidth = parent.width + if (viewportWidth > 0) { + // Set a temporary size to calculate the proper height + setSize(viewportWidth, Int.MAX_VALUE) + preferredSize.width = viewportWidth + preferredSize.height = super.getPreferredSize().height + } + } + return preferredSize + } + + override fun getMaximumSize(): Dimension { + val maxSize = super.getMaximumSize() + maxSize.width = Integer.MAX_VALUE + return maxSize + } +} + /** * PicoCode RAG Chat Window * Simple chat interface that communicates with PicoCode backend @@ -157,24 +190,121 @@ class PicoCodeToolWindowContent(private val project: Project) { return panel } + /** + * Convert markdown to HTML for rendering + * Note: Code block backgrounds use light gray which may need adjustment for dark themes + */ + private fun markdownToHtml(markdown: String): String { + var html = markdown + + // Process markdown constructs before escaping HTML + // Code blocks (```) - preserve content as-is + val codeBlockPlaceholders = mutableListOf() + html = html.replace(Regex("```([\\s\\S]*?)```")) { matchResult -> + val content = matchResult.groupValues[1] + val placeholder = "###CODEBLOCK${codeBlockPlaceholders.size}###" + codeBlockPlaceholders.add(content) + placeholder + } + + // Inline code (`) - preserve content + val inlineCodePlaceholders = mutableListOf() + html = html.replace(Regex("`([^`]+)`")) { matchResult -> + val content = matchResult.groupValues[1] + val placeholder = "###INLINECODE${inlineCodePlaceholders.size}###" + inlineCodePlaceholders.add(content) + placeholder + } + + // Escape HTML special characters in remaining text + html = html + .replace("&", "&") + .replace("<", "<") + .replace(">", ">") + + // Apply markdown formatting + html = html + // Bold (**text**) + .replace(Regex("\\*\\*([^*]+)\\*\\*"), "$1") + // Italic (*text*) + .replace(Regex("\\*([^*]+)\\*"), "$1") + // Headers + .replace(Regex("^### (.+)$", RegexOption.MULTILINE), "

$1

") + .replace(Regex("^## (.+)$", RegexOption.MULTILINE), "

$1

") + .replace(Regex("^# (.+)$", RegexOption.MULTILINE), "

$1

") + // Lists + .replace(Regex("^- (.+)$", RegexOption.MULTILINE), "
  • $1
  • ") + .replace(Regex("^\\* (.+)$", RegexOption.MULTILINE), "
  • $1
  • ") + + // Restore code blocks with proper styling and wrapping + codeBlockPlaceholders.forEachIndexed { index, content -> + val escapedContent = content + .replace("&", "&") + .replace("<", "<") + .replace(">", ">") + html = html.replace("###CODEBLOCK${index}###", + "
    $escapedContent
    ") + } + + // Restore inline code with proper styling and wrapping + inlineCodePlaceholders.forEachIndexed { index, content -> + val escapedContent = content + .replace("&", "&") + .replace("<", "<") + .replace(">", ">") + html = html.replace("###INLINECODE${index}###", + "$escapedContent") + } + + // Wrap consecutive list items in