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:

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())
<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 beforesetHtml().