[FEATURE] Consider using Prism.js for code rendering
"Prism is a lightweight, extensible syntax highlighter, built with modern web standards in mind. It’s used in millions of websites, including some of those you visit daily."
https://prismjs.com/
Applying Prism.js for markdown rendering in the DevoxxGenie plugin would be a great way to enhance code highlighting and improve the overall user experience. Let's go through the steps to integrate Prism.js into the DevoxxGenie plugin.
- Add Prism.js to your project: First, you'll need to add the Prism.js library to your project. This is done by including the Prism.js file in the resources.
// Add these files to your resources folder
/resources/assets/prism.js
/resources/assets/prism-hooks.js
- Create a utility class for HTML and syntax highlighting:
Introduce a
HtmlUtilclass that handles HTML generation and syntax highlighting:
package com.devoxx.genie.ui.util;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.util.Computable;
import org.jetbrains.annotations.NotNull;
import javax.script.*;
import java.io.InputStreamReader;
import java.util.List;
public class HtmlUtil {
private static final ScriptEngine engine;
static {
engine = new ScriptEngineManager().getEngineByName("graal.js");
try {
engine.eval(new InputStreamReader(HtmlUtil.class.getResourceAsStream("/assets/prism.js")));
engine.eval(new InputStreamReader(HtmlUtil.class.getResourceAsStream("/assets/prism-hooks.js")));
engine.eval("function highlight(code, language) { return Prism.highlight(code, Prism.languages[language], language); }");
} catch (ScriptException e) {
throw new RuntimeException("Failed to initialize Prism.js", e);
}
}
public static String highlightCode(String code, String language) {
return ApplicationManager.getApplication().runReadAction((Computable<String>) () -> {
try {
return (String) ((Invocable) engine).invokeFunction("highlight", code, language);
} catch (ScriptException | NoSuchMethodException e) {
throw new RuntimeException("Failed to highlight code", e);
}
});
}
// Add other HTML utility methods as needed
}
- Update your markdown rendering process:
In the DevoxxGenie plugin, you're currently using a custom markdown rendering process. You can enhance this by integrating Prism.js for code highlighting. Update your
ChatResponsePanelclass:
package com.devoxx.genie.ui.panel;
import com.devoxx.genie.ui.util.HtmlUtil;
import org.commonmark.node.*;
import org.commonmark.parser.Parser;
import org.commonmark.renderer.html.HtmlRenderer;
// ... other imports
public class ChatResponsePanel extends BackgroundPanel {
// ... other fields and methods
private void renderMarkdown(String markdown) {
Parser parser = Parser.builder().build();
Node document = parser.parse(markdown);
HtmlRenderer htmlRenderer = HtmlRenderer.builder()
.nodeRendererFactory(context -> new CodeBlockNodeRenderer(context))
.build();
String html = htmlRenderer.render(document);
// Apply the rendered HTML to your panel
// You might need to use a JEditorPane or similar component to display HTML
}
private class CodeBlockNodeRenderer implements NodeRenderer {
private final HtmlRenderer.HtmlNodeRendererContext context;
CodeBlockNodeRenderer(HtmlRenderer.HtmlNodeRendererContext context) {
this.context = context;
}
@Override
public Set<Class<? extends Node>> getNodeTypes() {
return new HashSet<>(Arrays.asList(FencedCodeBlock.class, IndentedCodeBlock.class));
}
@Override
public void render(Node node) {
if (node instanceof FencedCodeBlock) {
FencedCodeBlock codeBlock = (FencedCodeBlock) node;
String language = codeBlock.getInfo();
String code = codeBlock.getLiteral();
String highlightedCode = HtmlUtil.highlightCode(code, language);
context.getWriter().raw("<pre><code class=\"language-" + language + "\">" + highlightedCode + "</code></pre>");
} else if (node instanceof IndentedCodeBlock) {
// Handle indented code blocks similarly
}
}
}
}
- Update your CSS: To properly display the highlighted code, you'll need to include Prism.js CSS. You can either include it directly in your plugin or generate it dynamically. Here's an example of how to include it:
package com.devoxx.genie.ui.util;
public class PrismCssUtil {
public static String getPrismCss() {
return """
code[class*="language-"],
pre[class*="language-"] {
color: #f8f8f2;
background: none;
text-shadow: 0 1px rgba(0, 0, 0, 0.3);
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
font-size: 1em;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
/* Add more Prism.js CSS rules here */
""";
}
}
- Apply the CSS to your HTML output: When rendering your HTML, include the Prism.js CSS:
private String wrapHtmlContent(String htmlContent) {
return String.format("""
<!DOCTYPE html>
<html>
<head>
<style>
%s
</style>
</head>
<body>
%s
</body>
</html>
""", PrismCssUtil.getPrismCss(), htmlContent);
}
- Update your
ChatStreamingResponsePanel: If you're using streaming responses, you'll need to update theChatStreamingResponsePanelto use the new highlighting:
package com.devoxx.genie.ui.panel;
import com.devoxx.genie.ui.util.HtmlUtil;
import org.commonmark.node.*;
import org.commonmark.parser.Parser;
// ... other imports
public class ChatStreamingResponsePanel extends BackgroundPanel {
private final StringBuilder markdownContent = new StringBuilder();
private final JEditorPane editorPane;
// ... constructor and other methods
public void insertToken(String token) {
SwingUtilities.invokeLater(() -> {
markdownContent.append(token);
String html = renderMarkdown(markdownContent.toString());
editorPane.setText(html);
});
}
private String renderMarkdown(String markdown) {
Parser parser = Parser.builder().build();
Node document = parser.parse(markdown);
String html = renderNode(document);
return wrapHtmlContent(html);
}
private String renderNode(Node node) {
if (node instanceof FencedCodeBlock) {
FencedCodeBlock codeBlock = (FencedCodeBlock) node;
String language = codeBlock.getInfo();
String code = codeBlock.getLiteral();
return "<pre><code class=\"language-" + language + "\">" + HtmlUtil.highlightCode(code, language) + "</code></pre>";
}
// Handle other node types...
return "";
}
}
By implementing these changes, you'll be able to use Prism.js for syntax highlighting in your DevoxxGenie plugin. This will provide a more visually appealing and readable output for code snippets in your chat responses.
Remember to test thoroughly, as integrating JavaScript engines and modifying the rendering process can sometimes lead to unexpected behaviors or performance issues. You may need to fine-tune the implementation based on your specific requirements and the structure of your plugin.
IDEA Darcula theme https://github.com/LucaScorpion/prismjs-darcula-theme
Light theme @ https://github.com/joseluisgp/Prism-IntelliJ-Light