diff --git a/Components/CustomSlider.qml b/Components/CustomSlider.qml index 442b0d1..6e5f656 100644 --- a/Components/CustomSlider.qml +++ b/Components/CustomSlider.qml @@ -91,7 +91,7 @@ Slider { MaterialIcon { id: inset - readonly property bool attached: root.pos < 0.1 + readonly property bool attached: root.filledWidth < (inset.paintedWidth + Appearance.spacing.extraSmall * 2) property real dockT: attached ? 1 : 0 anchors.bottom: parent.bottom diff --git a/Helpers/ClipHistory.qml b/Helpers/ClipHistory.qml index 3c7fca4..fe38a37 100644 --- a/Helpers/ClipHistory.qml +++ b/Helpers/ClipHistory.qml @@ -20,10 +20,8 @@ Singleton { })) property string previewImageFile: "/tmp/qs-cliphist-preview.img" property string previewImageSource: "" - property bool previewIsCode: looksLikeCode(previewText) property bool previewIsImage: false - readonly property string previewMarkup: previewIsCode ? generateHighlightedMarkup(previewText) : previewText - property string previewText: "" + property list previewText: [] property int previewToken: 0 property real scoreThreshold: 0.2 @@ -59,119 +57,37 @@ Singleton { }); } - function generateHighlightedMarkup(text): string { - const raw = String(text ?? ""); - const lines = raw.split("\n").map(line => highlightCodeLine(line)); - return `
${lines.join("
")}
`; - } - - function highlightCodeLine(rawLine): string { - const line = String(rawLine ?? ""); - - const kwColor = DynamicColors.palette.m3primary; - const strColor = DynamicColors.palette.m3tertiary; - const comColor = Qt.alpha(DynamicColors.palette.m3onSurface, 0.55); - - const keywordRe = /\b(function|class|import|const|let|var|if|else|for|while|return|switch|case|break|continue|try|catch|throw|async|await|new|null|true|false|public|private|protected|static|extends|struct|enum)\b/g; - - let out = ""; - let i = 0; - - while (i < line.length) { - const ch = line[i]; - - // Line comment - if (line.slice(i, i + 2) === "//") { - out += `${escapeHtml(line.slice(i))}`; - break; - } - - // Shell/Python-style comment - if (ch === "#" && i === 0) { - out += `${escapeHtml(line.slice(i))}`; - break; - } - - // Quoted string - if (ch === "'" || ch === '"' || ch === "`") { - const quote = ch; - let j = i + 1; - while (j < line.length) { - if (line[j] === "\\") { - j += 2; - continue; - } - if (line[j] === quote) { - j += 1; - break; - } - j += 1; - } - - out += `${escapeHtml(line.slice(i, j))}`; - i = j; - continue; - } - - // Plain code text until next special token - let j = i; - while (j < line.length) { - const two = line.slice(j, j + 2); - const c = line[j]; - - if (two === "//") - break; - if (c === "'" || c === '"' || c === "`") - break; - if (c === "#" && j === 0) - break; - - j += 1; - } - - let segment = escapeHtml(line.slice(i, j)); - segment = segment.replace(keywordRe, `$1`); - out += segment; - i = j; - } - - return out === "" ? " " : out; - } - - function looksLikeCode(text): bool { - const t = String(text ?? "").trim(); - - if (t === "") - return false; - - const lines = t.split("\n"); - - if (lines.length < 2) - return false; - - let score = 0; - - for (const line of lines) { - if (/^\s{4,}|\t/.test(line)) - score += 2; - - if (/[{}()[\];]/.test(line)) - score += 1; - - if (/\b(function|class|import|const|let|var|if|else|for|while|return|switch|case|try|catch|async|await)\b/.test(line)) - score += 2; - - if (/=>|:=/.test(line)) - score += 1; - } - - return score >= 4; - } - function paste(entry): void { Quickshell.execDetached(["bash", "-c", `printf '${shellSingleQuoteEscape(entry)}' | ${root.cliphistBinary} decode | wl-copy && wl-paste`]); } + function processPreviewLines(text: string): var { + if (text === "") + return []; + const lines = text.split("\n"); + let minIndent = Infinity; + for (let i = 0; i < lines.length; i++) { + const m = lines[i].match(/^(\s*)/); + const indent = m ? m[1].length : 0; + if (lines[i].trim().length > 0 && indent < minIndent) + minIndent = indent; + } + if (minIndent === Infinity) + minIndent = 0; + const result = []; + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const indentMatch = line.match(/^(\s*)/); + const indentLen = indentMatch ? indentMatch[1].length : 0; + const stripped = line.slice(Math.min(minIndent, indentLen)); + result.push({ + line: i + 1, + text: stripped + }); + } + return result; + } + function refresh(): void { readProc.buffer = []; readProc.running = true; @@ -182,7 +98,7 @@ Singleton { const token = previewToken; if (!currentEntry) { - previewText = ""; + previewText = []; previewImageSource = ""; previewIsImage = false; return; @@ -269,7 +185,7 @@ Singleton { onStreamFinished: { if (previewTextProc.token !== root.previewToken) return; - root.previewText = this.text; + root.previewText = root.processPreviewLines(this.text); } } } diff --git a/Modules/Clipboard/Content.qml b/Modules/Clipboard/Content.qml index c918071..8df9f84 100644 --- a/Modules/Clipboard/Content.qml +++ b/Modules/Clipboard/Content.qml @@ -71,7 +71,7 @@ Item { anchors.leftMargin: Appearance.spacing.normal anchors.top: parent.top color: DynamicColors.tPalette.m3surfaceContainer - implicitWidth: ClipHistory.previewIsImage ? Math.max(Math.min(imagePreview.sourceSize.width + Appearance.padding.large * 2, Config.clipboard.sizes.previewWidth), Config.clipboard.sizes.minPreviewWidth) : Math.max(Math.min(textPreview.paintedWidth + textPreview.anchors.margins * 2, Config.clipboard.sizes.previewWidth), Config.clipboard.sizes.minPreviewWidth) + implicitWidth: ClipHistory.previewIsImage ? Math.max(Math.min(imagePreview.sourceSize.width + Appearance.padding.large * 2, Config.clipboard.sizes.previewWidth), Config.clipboard.sizes.minPreviewWidth) : Math.max(Math.min(textPreviewColumn.width + textPreviewColumn.anchors.margins * 2, Config.clipboard.sizes.previewWidth), Config.clipboard.sizes.minPreviewWidth) radius: 25 Behavior on implicitWidth { @@ -79,18 +79,105 @@ Item { } } - CustomText { - id: textPreview + Column { + id: textPreviewColumn anchors.left: parent.left - anchors.margins: Appearance.padding.large + anchors.leftMargin: 0 + anchors.margins: Appearance.padding.normal anchors.top: parent.top - font.family: ClipHistory.previewIsCode ? Appearance.font.family.mono : Appearance.font.family.sans - text: ClipHistory.previewMarkup - textFormat: ClipHistory.previewIsCode ? Text.RichText : Text.PlainText visible: !ClipHistory.previewIsImage - width: Config.clipboard.sizes.previewWidth - anchors.margins * 2 - wrapMode: Text.Wrap + + Repeater { + id: processedLines + + model: ClipHistory.previewText + + Row { + id: lineRow + + required property int index + required property var modelData + + spacing: Appearance.spacing.normal + + CustomRect { + color: lineRow.index % 2 ? DynamicColors.tPalette.m3surfaceContainer : "transparent" + implicitHeight: lineText.paintedHeight + Appearance.padding.extraSmall * 2 + implicitWidth: 50 + + CustomText { + id: number + + anchors.margins: Appearance.padding.large + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + color: DynamicColors.palette.m3onSurfaceVariant + font.pointSize: Appearance.font.size.small + text: lineRow.modelData.line + } + } + + Repeater { + model: lineRow.modelData.text.split("\t").slice(1) + + RowLayout { + height: lineText.height + width: lineText.height + Appearance.spacing.extraSmall + + Item { + Layout.fillHeight: true + Layout.fillWidth: true + + CustomRect { + anchors.centerIn: parent + color: DynamicColors.tPalette.m3surfaceContainer + implicitHeight: parent.height / 8 + implicitWidth: parent.height / 8 + radius: Appearance.rounding.full + } + } + + Item { + Layout.fillHeight: true + Layout.fillWidth: true + + CustomRect { + anchors.centerIn: parent + color: DynamicColors.tPalette.m3surfaceContainer + implicitHeight: parent.height / 8 + implicitWidth: parent.height / 8 + radius: Appearance.rounding.full + } + } + + Item { + Layout.fillHeight: true + Layout.fillWidth: true + + CustomRect { + anchors.centerIn: parent + color: DynamicColors.tPalette.m3surfaceContainer + implicitHeight: parent.height / 8 + implicitWidth: parent.height / 8 + radius: Appearance.rounding.full + } + } + } + } + + CustomText { + id: lineText + + color: DynamicColors.palette.m3onSurface + font.family: ClipHistory.previewIsCode ? Appearance.font.family.mono : Appearance.font.family.sans + height: lineText.paintedHeight + Appearance.padding.extraSmall * 2 + text: lineRow.modelData.text.trim() + verticalAlignment: Text.AlignVCenter + wrapMode: Text.Wrap + } + } + } } Image { @@ -258,6 +345,7 @@ Item { ClipHistory.currentEntry = currentItem.modelData; ClipHistory.refreshPreview(); } + onVisibleChanged: currentIndex = 0 CustomClippingWrapperRect { anchors.fill: parent diff --git a/Modules/Clipboard/Wrapper.qml b/Modules/Clipboard/Wrapper.qml index 6bac907..b1500a3 100644 --- a/Modules/Clipboard/Wrapper.qml +++ b/Modules/Clipboard/Wrapper.qml @@ -32,7 +32,6 @@ Item { active: root.shouldBeActive || root.visible anchors.centerIn: parent - asynchronous: true sourceComponent: Content { screen: root.screen diff --git a/Modules/Settings/Content.qml b/Modules/Settings/Content.qml index 392d1eb..dcca906 100644 --- a/Modules/Settings/Content.qml +++ b/Modules/Settings/Content.qml @@ -181,7 +181,7 @@ Item { } } - CustomRect { + CustomClippingRect { id: categoryContent anchors.bottom: parent.bottom @@ -197,7 +197,6 @@ Item { id: stack anchors.fill: parent - anchors.margins: Appearance.padding.extraSmall initialItem: general popEnter: Transition { diff --git a/Modules/Settings/Controls/SettingsPage.qml b/Modules/Settings/Controls/SettingsPage.qml index b3cae9c..e80cb54 100644 --- a/Modules/Settings/Controls/SettingsPage.qml +++ b/Modules/Settings/Controls/SettingsPage.qml @@ -23,18 +23,15 @@ Item { return false; } - CustomClippingWrapperRect { - anchors.fill: parent - child: flickable - radius: Appearance.rounding.normal - Appearance.padding.extraSmall - } - CustomFlickable { id: flickable anchors.fill: parent + // for future: + // anchors.leftMargin: Appearance.padding.extraLarge + // anchors.rightMargin: Appearance.padding.extraLarge clip: true - contentHeight: clayout.implicitHeight + contentHeight: clayout.implicitHeight + clayout.anchors.margins * 2 CustomScrollBar.vertical: CustomScrollBar { flickable: flickable @@ -64,7 +61,9 @@ Item { id: clayout anchors.left: parent.left + anchors.margins: Appearance.padding.extraSmall anchors.right: parent.right + anchors.top: parent.top spacing: Appearance.spacing.small // move: Transition { diff --git a/Modules/Settings/Controls/SettingsSection.qml b/Modules/Settings/Controls/SettingsSection.qml index 10976bb..5ed5d81 100644 --- a/Modules/Settings/Controls/SettingsSection.qml +++ b/Modules/Settings/Controls/SettingsSection.qml @@ -14,7 +14,7 @@ CustomRect { anchors.right: parent.right color: DynamicColors.tPalette.m3surfaceContainer implicitHeight: layout.height + contentPadding * 2 - radius: Appearance.rounding.normal - Appearance.padding.smaller + radius: Appearance.rounding.normal - Appearance.padding.extraSmall Behavior on implicitHeight { Anim {