Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Consider using Prism.js for code rendering #241

Open
stephanj opened this issue Aug 23, 2024 · 3 comments
Open

[Feature] Consider using Prism.js for code rendering #241

stephanj opened this issue Aug 23, 2024 · 3 comments
Labels
enhancement New feature or request

Comments

@stephanj
Copy link
Contributor

"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/

@stephanj stephanj added the enhancement New feature or request label Aug 23, 2024
@stephanj
Copy link
Contributor Author

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.

  1. 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
  1. Create a utility class for HTML and syntax highlighting:
    Introduce a HtmlUtil class 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
}
  1. 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 ChatResponsePanel class:
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
            }
        }
    }
}
  1. 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 */
        """;
    }
}
  1. 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);
}
  1. Update your ChatStreamingResponsePanel:
    If you're using streaming responses, you'll need to update the ChatStreamingResponsePanel to 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.

@stephanj
Copy link
Contributor Author

@stephanj
Copy link
Contributor Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant