async parsing + tex caching
This commit is contained in:
@@ -177,7 +177,14 @@ Item {
|
||||
sendIcon.icon: "arrow_upward"
|
||||
sendIcon.padding: Tokens.padding.extraSmall
|
||||
|
||||
onAccepted: root.send(text)
|
||||
Keys.onPressed: e => {
|
||||
if (e.key == Qt.Key_Return) {
|
||||
if (!(e.modifiers & Qt.ShiftModifier)) {
|
||||
root.send(text);
|
||||
e.accepted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
onSendPressed: root.send(text)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import ZShell.Config
|
||||
import qs.Components
|
||||
import qs.Services
|
||||
|
||||
TextFieldBase {
|
||||
TextAreaBase {
|
||||
id: root
|
||||
|
||||
readonly property alias bg: bg
|
||||
@@ -15,7 +15,7 @@ TextFieldBase {
|
||||
leftPadding: Tokens.padding.extraLarge
|
||||
rightPadding: sendIcon.width + sendIcon.anchors.rightMargin + Tokens.spacing.small
|
||||
topPadding: Tokens.padding.large
|
||||
wrapMode: TextFieldBase.Wrap
|
||||
wrapMode: TextAreaBase.Wrap
|
||||
|
||||
background: CustomRect {
|
||||
id: bg
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import QtQuick
|
||||
import QtQuick.Shapes
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import ZShell.Config
|
||||
@@ -12,57 +13,69 @@ CustomClippingRect {
|
||||
required property string language
|
||||
required property string code
|
||||
property bool copied: false
|
||||
property color codeColor: Colors.palette.m3onSurfaceVariant
|
||||
property color codeBackgroundColor: Colors.palette.m3surfaceContainerLow
|
||||
property color codeBackgroundColor: Colors.palette.m3surfaceContainerHigh
|
||||
property color codeHeaderColor: Colors.palette.m3outline
|
||||
property color codeAccentColor: Colors.palette.m3primary
|
||||
|
||||
// Highlighter spans for the current code; refreshed when the code or
|
||||
// its language changes.
|
||||
// its language changes. Highlighting runs off the GUI thread; the
|
||||
// token drops results that arrive after the code already changed.
|
||||
property var codeSpans: []
|
||||
property int highlightToken: 0
|
||||
|
||||
function refresh() {
|
||||
codeSpans = CodeHighlighter.highlight(root.code, root.language);
|
||||
const token = ++root.highlightToken;
|
||||
codeSpans = [];
|
||||
CodeHighlighter.highlight(root.code, root.language, root, token);
|
||||
}
|
||||
|
||||
function onHighlightSpans(token, spans) {
|
||||
if (token !== root.highlightToken)
|
||||
return;
|
||||
codeSpans = spans;
|
||||
}
|
||||
|
||||
function roleColor(kind) {
|
||||
var s = CodeColors.active;
|
||||
switch (kind) {
|
||||
case "comment":
|
||||
return Colors.palette.m3outline;
|
||||
return s.comment;
|
||||
case "string":
|
||||
return Colors.palette.m3tertiary;
|
||||
return s.string;
|
||||
case "string.key":
|
||||
return Colors.palette.m3secondary;
|
||||
return s.stringKey;
|
||||
case "number":
|
||||
case "constant":
|
||||
return Colors.palette.m3tertiaryFixed;
|
||||
return s.number;
|
||||
case "keyword":
|
||||
return root.codeAccentColor;
|
||||
return s.keyword;
|
||||
case "type":
|
||||
return Colors.palette.m3secondary;
|
||||
return s.type;
|
||||
case "function":
|
||||
return s.functions;
|
||||
case "method":
|
||||
return Colors.palette.m3onSurface;
|
||||
return s.method ?? s.functions;
|
||||
case "macro":
|
||||
return s.macro;
|
||||
case "preproc":
|
||||
return Colors.palette.m3secondaryContainer;
|
||||
return s.preproc ?? s.macro;
|
||||
case "operator":
|
||||
return s.operator ?? s.normal;
|
||||
case "property":
|
||||
return root.codeColor;
|
||||
return s.property ?? s.normal;
|
||||
case "label":
|
||||
return s.label;
|
||||
case "attribute":
|
||||
return Colors.palette.m3tertiaryFixedDim;
|
||||
return s.attribute ?? s.label;
|
||||
default:
|
||||
return root.codeColor;
|
||||
return s.normal;
|
||||
}
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
function escapeHtml(text): string {
|
||||
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
||||
}
|
||||
|
||||
// Wraps the code in <font color> tags following the highlighter spans.
|
||||
function highlightedHtml(code, spans) {
|
||||
function highlightedHtml(code, spans): string {
|
||||
let out;
|
||||
if (!spans.length) {
|
||||
out = escapeHtml(code);
|
||||
@@ -79,37 +92,17 @@ CustomClippingRect {
|
||||
if (pos < code.length)
|
||||
out += escapeHtml(code.slice(pos));
|
||||
}
|
||||
// RichText collapses HTML whitespace: break newlines, and protect
|
||||
// line-leading indentation with nbsp (internal spaces stay normal
|
||||
// so long lines can still wrap).
|
||||
return out
|
||||
.replace(/(^|\n)[ \t]+/g, ws => ws.replace(/[ \t]/g, " "))
|
||||
.replace(/\n/g, "<br>");
|
||||
return out.replace(/(^|\n)[ \t]+/g, ws => ws.replace(/[ \t]/g, " ")).replace(/\n/g, "<br>");
|
||||
}
|
||||
|
||||
implicitWidth: headerRow.implicitWidth + headerRow.anchors.leftMargin + headerRow.anchors.rightMargin
|
||||
implicitHeight: headerRow.anchors.topMargin + headerRow.implicitHeight + codeText.implicitHeight + codeText.anchors.margins * 2
|
||||
implicitHeight: headerRow.anchors.topMargin + headerRow.implicitHeight + codeRect.implicitHeight + codeRect.anchors.margins + codeRect.anchors.topMargin
|
||||
color: root.codeBackgroundColor
|
||||
radius: Tokens.rounding.medium
|
||||
|
||||
onLanguageChanged: refresh()
|
||||
onCodeChanged: refresh()
|
||||
|
||||
CustomText {
|
||||
id: codeText
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.top: headerRow.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Tokens.padding.small
|
||||
color: root.codeColor
|
||||
font.family: Config.appearance.font.family.mono
|
||||
font.pointSize: Tokens.font.size.small
|
||||
textFormat: Text.RichText
|
||||
text: root.highlightedHtml(root.code, root.codeSpans)
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: headerRow
|
||||
|
||||
@@ -121,11 +114,74 @@ CustomClippingRect {
|
||||
anchors.rightMargin: Tokens.padding.small
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
Item {
|
||||
id: iconItem
|
||||
|
||||
readonly property string iconPath: CodeIcons.path(root.language)
|
||||
|
||||
Layout.fillHeight: true
|
||||
implicitWidth: height
|
||||
|
||||
Shape {
|
||||
id: shape
|
||||
|
||||
anchors.centerIn: parent
|
||||
visible: iconItem.iconPath !== ""
|
||||
preferredRendererType: Shape.CurveRenderer
|
||||
scale: Math.min(langText.implicitHeight / height, langText.implicitHeight / width)
|
||||
|
||||
ShapePath {
|
||||
strokeColor: "transparent"
|
||||
fillColor: Colors.palette.m3tertiary
|
||||
fillRule: ShapePath.WindingFill
|
||||
|
||||
PathSvg {
|
||||
path: iconItem.iconPath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Shape {
|
||||
id: fallbackShape
|
||||
|
||||
anchors.centerIn: parent
|
||||
anchors.verticalCenterOffset: -1
|
||||
visible: iconItem.iconPath === ""
|
||||
preferredRendererType: Shape.CurveRenderer
|
||||
scale: Math.min(langText.implicitHeight / height, langText.implicitHeight / width)
|
||||
|
||||
ShapePath {
|
||||
strokeColor: Colors.palette.m3tertiary
|
||||
strokeWidth: 16
|
||||
capStyle: ShapePath.RoundCap
|
||||
joinStyle: ShapePath.RoundJoin
|
||||
fillColor: "transparent"
|
||||
|
||||
PathSvg {
|
||||
path: "M 40 64 L 112 128 L 40 192"
|
||||
}
|
||||
}
|
||||
|
||||
ShapePath {
|
||||
strokeColor: Colors.palette.m3tertiary
|
||||
strokeWidth: 16
|
||||
capStyle: ShapePath.RoundCap
|
||||
fillColor: "transparent"
|
||||
|
||||
PathSvg {
|
||||
path: "M 120 192 L 216 192"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomText {
|
||||
id: langText
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
color: root.codeHeaderColor
|
||||
font.pointSize: Tokens.font.size.small
|
||||
text: root.language
|
||||
text: CodeIcons.name(root.language)
|
||||
visible: root.language.length > 0
|
||||
}
|
||||
|
||||
@@ -137,6 +193,7 @@ CustomClippingRect {
|
||||
icon: root.copied ? "check" : "content_copy"
|
||||
inactiveColor: "transparent"
|
||||
inactiveOnColor: root.codeHeaderColor
|
||||
label.animate: true
|
||||
type: IconButton.Text
|
||||
|
||||
onClicked: {
|
||||
@@ -154,4 +211,40 @@ CustomClippingRect {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomRect {
|
||||
id: codeRect
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.top: headerRow.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.topMargin: Tokens.padding.small
|
||||
radius: root.radius - anchors.margins
|
||||
anchors.margins: Tokens.padding.extraSmall
|
||||
implicitWidth: codeText.implicitWidth + codeFlick.anchors.margins * 2
|
||||
implicitHeight: codeText.implicitHeight + codeFlick.anchors.margins * 2
|
||||
color: CodeColors.active.bg
|
||||
|
||||
Flickable {
|
||||
id: codeFlick
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.padding.small
|
||||
|
||||
CustomScrollBar.horizontal: CustomScrollBar {
|
||||
flickable: codeFlick
|
||||
}
|
||||
TextAreaBase.flickable: TextAreaBase {
|
||||
id: codeText
|
||||
|
||||
color: CodeColors.active.normal
|
||||
font.family: Config.appearance.font.family.mono
|
||||
font.pointSize: Tokens.font.size.small
|
||||
textFormat: Text.RichText
|
||||
text: root.highlightedHtml(root.code, root.codeSpans)
|
||||
readOnly: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
pragma Singleton
|
||||
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import QtQuick
|
||||
import ZShell.Config
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
readonly property JsonObject schemes: JsonObject {
|
||||
readonly property Scheme oneDark: Scheme {
|
||||
comment: "#5c6370"
|
||||
string: "#98c379"
|
||||
stringKey: "#e06c75"
|
||||
number: "#d19a66"
|
||||
keyword: "#c678dd"
|
||||
type: "#e5c07b"
|
||||
functions: "#61afef"
|
||||
method: "#61afef"
|
||||
macro: "#98c379"
|
||||
preproc: "#abb2bf"
|
||||
operator: "#abb2bf"
|
||||
property: "#abb2bf"
|
||||
label: "#e06c75"
|
||||
attribute: "#d19a66"
|
||||
bg: "#282c34"
|
||||
normal: "#abb2bf"
|
||||
}
|
||||
readonly property Scheme nord: Scheme {
|
||||
comment: "#4c566a"
|
||||
string: "#a3be8c"
|
||||
stringKey: "#ebcb8b"
|
||||
number: "#b48ead"
|
||||
keyword: "#81a1c1"
|
||||
type: "#8fbcbb"
|
||||
functions: "#88c0d0"
|
||||
method: "#88c0d0"
|
||||
macro: "#5e81ac"
|
||||
preproc: "#5e81ac"
|
||||
operator: "#81a1c1"
|
||||
property: "#d8dee9"
|
||||
label: "#d08770"
|
||||
attribute: "#d8dee9"
|
||||
bg: "#2e3440"
|
||||
normal: "#d8dee9"
|
||||
}
|
||||
readonly property Scheme dracula: Scheme {
|
||||
comment: "#6272a4"
|
||||
string: "#f1fa8c"
|
||||
stringKey: "#f8f8f2"
|
||||
number: "#ffb86c"
|
||||
keyword: "#ff79c6"
|
||||
type: "#8be9fd"
|
||||
functions: "#50fa7b"
|
||||
method: "#50fa7b"
|
||||
macro: "#ff79c6"
|
||||
preproc: "#ff79c6"
|
||||
operator: "#f8f8f2"
|
||||
property: "#f8f8f2"
|
||||
label: "#6272a4"
|
||||
attribute: "#8be9fd"
|
||||
bg: "#282a36"
|
||||
normal: "#f8f8f2"
|
||||
}
|
||||
readonly property Scheme githubDark: Scheme {
|
||||
comment: "#8b949e"
|
||||
string: "#a5d6ff"
|
||||
stringKey: "#79c0ff"
|
||||
number: "#79c0ff"
|
||||
keyword: "#ff7b72"
|
||||
type: "#ffa657"
|
||||
functions: "#d2a8ff"
|
||||
method: "#d2a8ff"
|
||||
macro: "#ff7b72"
|
||||
preproc: "#79c0ff"
|
||||
operator: "#ff7b72"
|
||||
property: "#79c0ff"
|
||||
label: "#7ee787"
|
||||
attribute: "#7ee787"
|
||||
bg: "#0d1117"
|
||||
normal: "#c9d1d9"
|
||||
}
|
||||
readonly property Scheme solarizedDark: Scheme {
|
||||
comment: "#586e75"
|
||||
string: "#2aa198"
|
||||
stringKey: "#268bd2"
|
||||
number: "#2aa198"
|
||||
keyword: "#859900"
|
||||
type: "#b58900"
|
||||
functions: "#268bd2"
|
||||
method: "#268bd2"
|
||||
macro: "#cb4b16"
|
||||
preproc: "#cb4b16"
|
||||
operator: "#859900"
|
||||
property: "#268bd2"
|
||||
label: "#6c71c4"
|
||||
attribute: "#657b83"
|
||||
bg: "#002b36"
|
||||
normal: "#839496"
|
||||
}
|
||||
readonly property Scheme monokai: Scheme {
|
||||
comment: "#75715e"
|
||||
string: "#e6db74"
|
||||
stringKey: "#f8f8f2"
|
||||
number: "#ae81ff"
|
||||
keyword: "#f92672"
|
||||
type: "#a6e22e"
|
||||
functions: "#a6e22e"
|
||||
method: "#a6e22e"
|
||||
macro: "#a6e22e"
|
||||
preproc: "#f92672"
|
||||
operator: "#f92672"
|
||||
property: "#fda5ff"
|
||||
label: "#f92672"
|
||||
attribute: "#a6e22e"
|
||||
bg: "#272822"
|
||||
normal: "#f8f8f2"
|
||||
}
|
||||
readonly property Scheme gruvboxDark: Scheme {
|
||||
comment: "#928374"
|
||||
string: "#b8bb26"
|
||||
stringKey: "#ebdbb2"
|
||||
number: "#d3869b"
|
||||
keyword: "#fb4934"
|
||||
type: "#fabd2f"
|
||||
functions: "#b8bb26"
|
||||
method: "#b8bb26"
|
||||
macro: "#8ec07c"
|
||||
preproc: "#8ec07c"
|
||||
operator: "#ebdbb2"
|
||||
property: "#83a598"
|
||||
label: "#fb4934"
|
||||
attribute: "#8ec07c"
|
||||
bg: "#1d2021"
|
||||
normal: "#ebdbb2"
|
||||
}
|
||||
readonly property Scheme catppuccinMocha: Scheme {
|
||||
comment: "#9399b2"
|
||||
string: "#a6e3a1"
|
||||
stringKey: "#b4befe"
|
||||
number: "#fab387"
|
||||
keyword: "#cba6f7"
|
||||
type: "#f9e2af"
|
||||
functions: "#89b4fa"
|
||||
method: "#89b4fa"
|
||||
macro: "#cba6f7"
|
||||
preproc: "#f5c2e7"
|
||||
operator: "#89dceb"
|
||||
property: "#b4befe"
|
||||
label: "#74c7ec"
|
||||
attribute: "#f9e2af"
|
||||
bg: "#1e1e2e"
|
||||
normal: "#cdd6f4"
|
||||
}
|
||||
readonly property Scheme tokyoNight: Scheme {
|
||||
comment: "#565f89"
|
||||
string: "#9ece6a"
|
||||
stringKey: "#73daca"
|
||||
number: "#ff9e64"
|
||||
keyword: "#9d7cd8"
|
||||
type: "#2ac3de"
|
||||
functions: "#7aa2f7"
|
||||
method: "#7aa2f7"
|
||||
macro: "#7dcfff"
|
||||
preproc: "#7dcfff"
|
||||
operator: "#89ddff"
|
||||
property: "#73daca"
|
||||
label: "#7aa2f7"
|
||||
attribute: "#73daca"
|
||||
bg: "#1a1b26"
|
||||
normal: "#c0caf5"
|
||||
}
|
||||
readonly property Scheme ayuDark: Scheme {
|
||||
comment: "#5a6673"
|
||||
string: "#aad94c"
|
||||
stringKey: "#aad94c"
|
||||
number: "#d2a6ff"
|
||||
keyword: "#ff8f40"
|
||||
type: "#59c2ff"
|
||||
functions: "#ffb454"
|
||||
method: "#ffb454"
|
||||
macro: "#59c2ff"
|
||||
preproc: "#ff8f40"
|
||||
operator: "#f29668"
|
||||
property: "#f07178"
|
||||
label: "#59c2ff"
|
||||
attribute: "#ffb454"
|
||||
bg: "#0a0e14"
|
||||
normal: "#bfbdb6"
|
||||
}
|
||||
readonly property Scheme palenight: Scheme {
|
||||
comment: "#697098"
|
||||
string: "#c3e88d"
|
||||
stringKey: "#82b1ff"
|
||||
number: "#f78c6c"
|
||||
keyword: "#ff5370"
|
||||
type: "#ffcb6b"
|
||||
functions: "#82b1ff"
|
||||
method: "#82b1ff"
|
||||
macro: "#c792ea"
|
||||
preproc: "#ffcb6b"
|
||||
operator: "#89ddff"
|
||||
property: "#c3e88d"
|
||||
label: "#c792ea"
|
||||
attribute: "#ffcb6b"
|
||||
bg: "#292d3e"
|
||||
normal: "#bfc7d5"
|
||||
}
|
||||
}
|
||||
readonly property var active: schemes[Config.llm.appearance.scheme]
|
||||
|
||||
component Scheme: JsonObject {
|
||||
property color comment: "#697098"
|
||||
property color string: "#c3e88d"
|
||||
property color stringKey: "#82b1ff"
|
||||
property color number: "#f78c6c"
|
||||
property color keyword: "#ff5370"
|
||||
property color type: "#ffcb6b"
|
||||
property color functions: "#82b1ff"
|
||||
property color method: "#82b1ff"
|
||||
property color macro: "#c792ea"
|
||||
property color preproc: "#ffcb6b"
|
||||
property color operator: "#89ddff"
|
||||
property color property: "#c3e88d"
|
||||
property color label: "#c792ea"
|
||||
property color attribute: "#ffcb6b"
|
||||
property color bg: "#292d3e"
|
||||
property color normal: "#bfc7d5"
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -30,7 +30,7 @@ Item {
|
||||
radius: Tokens.rounding.medium
|
||||
color: root.isUser ? Colors.palette.m3primary : Colors.palette.m3surfaceContainer
|
||||
implicitWidth: root.isUser ? Math.min(msgText.implicitWidth + Tokens.padding.medium * 2, root.contentMaxWidth) : root.contentMaxWidth
|
||||
implicitHeight: root.isUser ? msgText.contentHeight + Tokens.padding.medium * 2 : blocks.implicitHeight + Tokens.padding.medium * 2
|
||||
implicitHeight: root.isUser ? msgText.contentHeight + Tokens.padding.medium * 2 : blocks.implicitHeight + blocks.anchors.topMargin * 2
|
||||
anchors.right: root.isUser ? parent.right : undefined
|
||||
|
||||
// User messages stay a plain editable text field.
|
||||
@@ -92,10 +92,10 @@ Item {
|
||||
id: blocks
|
||||
|
||||
visible: !root.isUser
|
||||
anchors.topMargin: Tokens.padding.medium
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Tokens.padding.medium
|
||||
blocks: root.segment.markdown
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ Column {
|
||||
language: modelData.language
|
||||
code: modelData.code
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Tokens.spacing.small
|
||||
anchors.left: parent.left
|
||||
}
|
||||
}
|
||||
@@ -42,6 +43,7 @@ Column {
|
||||
latex: modelData.latex
|
||||
anchors.right: parent.right
|
||||
anchors.left: parent.left
|
||||
anchors.margins: Tokens.padding.medium
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +56,7 @@ Column {
|
||||
color: Colors.palette.m3onSurface
|
||||
anchors.right: parent.right
|
||||
anchors.left: parent.left
|
||||
anchors.margins: Tokens.padding.medium
|
||||
text: modelData.text
|
||||
textFormat: Text.MarkdownText
|
||||
font.bold: true
|
||||
@@ -77,6 +80,7 @@ Column {
|
||||
color: Colors.palette.m3onSurface
|
||||
anchors.right: parent.right
|
||||
anchors.left: parent.left
|
||||
anchors.margins: Tokens.padding.medium
|
||||
text: modelData.text
|
||||
textFormat: Text.MarkdownText
|
||||
font.pointSize: Tokens.font.size.smaller
|
||||
|
||||
Reference in New Issue
Block a user