// Copyright (C) 2026 Danilo M. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License version 2 as // published by the Free Software Foundation. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. import Quickshell import QtQuick import ".." import "Md.js" as Md // Markdown with a copy button per code block. Prose is a read-only TextEdit, // selectable unlike Text. It goes through Notify.sanitize, which blocks // remote images: model output can quote a search result, and an image URL // in it would be fetched the moment it rendered. Column { id: seg property string text spacing: 8 Repeater { model: Md.split(seg.text) Item { id: part required property var modelData width: seg.width height: modelData.kind === "code" ? block.height : prose.height TextEdit { id: prose visible: part.modelData.kind === "prose" width: part.width height: visible ? implicitHeight : 0 readOnly: true selectByMouse: true textFormat: TextEdit.MarkdownText text: visible ? Notify.sanitize(part.modelData.text) : "" wrapMode: TextEdit.Wrap color: Theme.text selectionColor: Qt.alpha(Theme.accent, 0.4) font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2 } onLinkActivated: link => Qt.openUrlExternally(link) HoverHandler { cursorShape: prose.hoveredLink ? Qt.PointingHandCursor : Qt.IBeamCursor } } Rectangle { id: block visible: part.modelData.kind === "code" width: part.width height: visible ? code.height + 16 : 0 radius: 8 color: Qt.alpha(Theme.crust, 0.6) TextEdit { id: code anchors { left: parent.left; right: copy.left; top: parent.top; margins: 8 } readOnly: true selectByMouse: true textFormat: TextEdit.PlainText text: part.modelData.text ?? "" wrapMode: TextEdit.Wrap color: Theme.text selectionColor: Qt.alpha(Theme.accent, 0.4) font { family: Theme.iconFamily; pixelSize: Theme.fontSize - 3 } } // Copy only a finished block: a half-streamed one is not the // command the model meant. Button { id: copy anchors { right: parent.right; top: parent.top; margins: 6 } visible: !part.modelData.open width: visible ? implicitWidth : 0 text: copied.running ? "Copied" : (part.modelData.lang || "Copy") onClicked: { Quickshell.execDetached(["wl-copy", "--", part.modelData.text]); copied.restart(); } Timer { id: copied; interval: 1200 } } } } } }