0

I am trying to create a UI that resembles OpenAI's, where code and markdown snippets follow each other and markdown and the code are parsed/syntax highlighted accordingly. I've been using PyQt6 and my subclass of QTextEdit to resemble the text snippets that may either be code or plain text (not markdown for now).

Syntax highlighting and markdown parsing aside, I am failing to understand why FormattedSnippet does not take as much space as it can in the QVBoxLayout. By changing the size of my test_doc (with *=) I got the impression that the FormattedSnippet widgets squeeze each other as I add more of them to Window. Which results in my FormattedSnippets being scrollable. Instead, I need the Window to expand as I add each FormattedSnippet and I the user should be able to see each snippet by scrolling through Window, not the individual FormattedSnippets

You can see everything I have been working with below (except my QSyntaxHighlighter subclass because it isn't relevant), though the problem probably lies in the FormattedSnippet or Window classes. I tried subclassing QLabel instead of QTextEdit instead since it is not scrollable and it actually lays out its content fully, just as I want it to. However, as I understand it, QLabel does not support rich text so syntax highlighting is off the table.

I am pretty new to PyQt6 so please correct me if any of my explanations/understandings are wrong.

Here is what the UI currently looks like. Each snippet has its scroll bar on the far-right: Current state of the UI + highlighting

class Snippet:
    class Type(Enum):
        CODE = "CODE"
        PLAINTEXT = "PLAINTEXT"

    def __init__(self, type: Type, text: str):
        self.type = type
        self.text = text

class FormattedSnippet(QTextEdit):
    def __init__(self, snippet: Snippet):
        super().__init__()
        self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Maximum)
        self.setReadOnly(True)
        
        if snippet.type == Snippet.Type.CODE:
            language, code = parse_snippet(snippet.text)
            self.setText(code)

            font = QFont("Courier New")
            font.setStyleHint(QFont.StyleHint.Monospace)
            self.setFont(font)
            self.setStyleSheet(
                """
                QTextEdit {
                    background-color: #000000;
                    color: #dcdcdc;
                    border-radius: 4px;
                    padding: 4px;
                }
            """
            )
        else:
            self.setText(snippet.text)
            self.setStyleSheet(
                """
                QTextEdit {
                    background-color: transparent;
                    color: #E0E0E0;
                    border: none;
                    padding: 4px;
                }
            """
            )
        
def parse_snippet(snippet: str):
    """
    Parse a fenced code block:
    ```lang
    code here
    ```
    Returns (language, code).
    """
    pattern = re.compile(r"```(\w+)\s*(.*?)\s*```", re.DOTALL)
    match = re.search(pattern, snippet.strip())
    if match:
        lang, code = match.groups()
        return lang.strip(), code.strip()
    return "text", snippet  # fallback


def extract_snippets(document: str) -> list[Snippet]:
    # Split the document by code fence markers
    parts = re.split(r"(```\w+[\s\S]*?```)", document)

    snippets = []
    for part in parts:
        if part.strip():
            if part.startswith("```") and part.endswith("```"):
                snippets.append(Snippet(Snippet.Type.CODE, part))
            else:
                snippets.append(Snippet(Snippet.Type.PLAINTEXT, part))

    return snippets

class Window(QScrollArea):
    def __init__(self, test_doc):
        super().__init__()

        layout = QtWidgets.QVBoxLayout(self)
        frame = QtWidgets.QFrame()
        self.setWidgetResizable(True)
        frame.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
        self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
        frame.setLayout(layout)
        self.setWidget(frame)
        snippets = extract_snippets(test_doc)
        for snippet in snippets:
            formatted_snippet = FormattedSnippet(snippet)
            layout.addWidget(formatted_snippet)

if __name__ == "__main__":
    test_doc = """Here is some text
Here is some text
Here is some text
Here is some text
Here is some text
    ```python
    def hello(document: str):
        print("world")  # Prints 'world'
        print("world")
        a = "hello"
        print(a)
        
    def test():
        return
    ```
    
    More text here
    
    ```java
    import java.util.Random;

public class RandomSnippet {

    public static void main(String[] args) {
        // Generate a random element from an array
        String[] colors = {"red", "green", "blue"};
        int randomIndex = random.nextInt(colors.length);
        String randomColor = colors[randomIndex];
        System.out.println("Random color: " + randomColor);


        // Example of generating multiple random numbers
        System.out.println("Generating 5 random numbers:");
        for (int i = 0; i < 5; i++) {
            System.out.println(random.nextInt(100)); // Generates random numbers between 0 and 99
        }
    }
}

    ```
    Final text"""

    test_doc *= 1
    app = QtWidgets.QApplication(["Test"])
    window = Window(test_doc)
    window.resize(800, 600)
    window.show()
    sys.exit(app.exec())

2
  • 2
    There's really little point in using multiple text edits: you can just use one, which will be much easier to manage and would also allow proper text selection. The Qt rich text support is relatively limited, but works perfectly fine for such tasks: use a properly formatted HTML content with code within the <pre> or <code> tags, and style sheets for those types, either with full HTML doc syntax or by setting the default style sheet on the QTextEdit's document before setHtml(). Commented Aug 18 at 19:19
  • @musicamante Actually never thought of that. It is much easier to maintain text blocks now, thank you. Commented Aug 19 at 8:40

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.