new chatlistview component, file attachments, performance & primitive test

This commit is contained in:
2026-09-30 15:01:28 +02:00
parent 51ed270774
commit d8a3d5732e
36 changed files with 4368 additions and 310 deletions
@@ -16,10 +16,15 @@ CustomRect {
property bool copied: false
property color codeBackgroundColor: Colors.palette.m3surfaceContainerHigh
property color codeHeaderColor: Colors.palette.m3outline
property var codeSpans: []
property string html: ""
property bool colored: false
property string requestedCode: ""
property string requestedLanguage: ""
property int highlightToken: 0
function refresh() {
requestedCode = root.code;
requestedLanguage = root.language;
const token = ++root.highlightToken;
CodeHighlighter.highlight(root.code, root.language, root, token);
}
@@ -27,7 +32,11 @@ CustomRect {
function onHighlightSpans(token, spans) {
if (token !== root.highlightToken)
return;
codeSpans = spans;
if (root.code !== root.requestedCode ||
root.language !== root.requestedLanguage)
return;
html = spans.length ? root.highlightedHtml(root.code, spans) : "";
colored = root.visible && html.length > 0;
}
function roleColor(kind) {
@@ -71,6 +80,21 @@ CustomRect {
return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}
// Mirrors the whitespace rules of the rich text rendering below (leading
// whitespace kept, mid-line runs collapsed, tabs as single spaces) so the
// plain and colored layouts are identical.
function plainForDisplay(code): string {
return code.split("\n")
.map(line => {
const i = line.search(/\S/);
if (i < 0)
return "";
const lead = line.slice(0, i).replace(/\t/g, " ");
return lead + line.slice(i).replace(/\s+/g, " ").replace(/\s+$/, "");
})
.join("\n");
}
function highlightedHtml(code, spans): string {
let out;
if (!spans.length) {
@@ -101,10 +125,26 @@ CustomRect {
radius: Tokens.rounding.medium
onLanguageChanged: {
codeSpans = [];
refresh();
html = "";
colored = false;
if (root.visible)
refreshTimer.restart();
}
onCodeChanged: {
colored = false;
if (root.visible)
refreshTimer.restart();
}
onVisibleChanged: if (root.visible)
refreshTimer.restart();
Timer {
id: refreshTimer
interval: 200
repeat: false
onTriggered: refresh()
}
onCodeChanged: refresh()
RowLayout {
id: headerRow
@@ -229,21 +269,6 @@ CustomRect {
implicitHeight: codeText.implicitHeight + codeFlick.anchors.margins * 2
color: CodeColors.active.bg
CustomText {
id: code
anchors.top: parent.top
anchors.bottom: parent.bottom
x: implicitWidth * (0 - codeFlick.visibleArea.xPosition) + Tokens.padding.small
anchors.margins: Tokens.padding.small
text: root.highlightedHtml(root.code, root.codeSpans)
textFormat: Text.RichText
color: CodeColors.active.normal
layer.enabled: true
clip: false
font.family: Config.appearance.font.family.mono
font.pointSize: Tokens.font.size.small
}
Flickable {
id: codeFlick
@@ -263,12 +288,11 @@ CustomRect {
color: CodeColors.active.normal
layer.enabled: true
leftInset: Tokens.padding.small
clip: false
font.family: Config.appearance.font.family.mono
font.pointSize: Tokens.font.size.small
textFormat: Text.RichText
text: code.text
textFormat: root.colored ? Text.RichText : Text.PlainText
text: root.colored ? root.html : root.plainForDisplay(root.code)
readOnly: true
}
}