summaryrefslogtreecommitdiff
path: root/kodereviewer
diff options
context:
space:
mode:
Diffstat (limited to 'kodereviewer')
-rw-r--r--kodereviewer/models/label.py96
-rw-r--r--kodereviewer/models/line_model.py81
-rw-r--r--kodereviewer/qml/Editor.qml100
-rw-r--r--kodereviewer/qml/FilesView.qml61
-rw-r--r--kodereviewer/qml/MarkdownTextArea.qml20
5 files changed, 358 insertions, 0 deletions
diff --git a/kodereviewer/models/label.py b/kodereviewer/models/label.py
new file mode 100644
index 0000000..8349aa8
--- /dev/null
+++ b/kodereviewer/models/label.py
@@ -0,0 +1,96 @@
+from copy import copy
+from enum import IntEnum, auto
+from typing import Any, Optional
+
+from PySide6.QtCore import QAbstractListModel, QByteArray, QModelIndex, QObject, QPersistentModelIndex, QStandardPaths, QUrl, Qt, Signal, Slot, Property
+from PySide6.QtQml import QmlElement
+
+from kodereviewer.project import Project
+from kodereviewer.data import Label, PullRequest
+
+QML_IMPORT_NAME = "org.deprecated.kodereviewer"
+QML_IMPORT_MAJOR_VERSION = 1
+
+
+@QmlElement
+class LabelModel(QAbstractListModel):
+ """Model to show pull request labels."""
+
+ _pull_request: Optional[PullRequest]
+
+ class Roles(IntEnum):
+ Name = Qt.ItemDataRole.UserRole + 1
+ LabelColor = auto()
+ TextColor = auto()
+ Description = auto()
+
+ def __init__(self):
+ super().__init__()
+ self._pull_request = None
+
+ pullRequestChanged = Signal()
+
+ def get_pull_request(self) -> Optional[PullRequest]:
+ return self._pull_request
+
+ def set_pull_request(self, pull_request: Optional[PullRequest]) -> None:
+ if pull_request is None:
+ return
+
+ if self._pull_request is not None and self._pull_request == pull_request:
+ return
+
+ self.beginResetModel()
+ self._pull_request = pull_request
+ self.endResetModel()
+ self.pullRequestChanged.emit()
+
+ pullRequest = Property(PullRequest, fget=get_pull_request, fset=set_pull_request,
+ notify=pullRequestChanged)
+
+ def data(self,
+ index: QModelIndex | QPersistentModelIndex,
+ role: int = Qt.ItemDataRole.DisplayRole) -> object:
+ if self._pull_request is None:
+ return None
+
+ label: Label = self._pull_request.labels[index.row()]
+
+ if role == self.Roles.Name:
+ return label.name
+ if role == self.Roles.LabelColor:
+ return f'#{label.color}'
+ if role == self.Roles.TextColor:
+ return self.text_color(label.color)
+ if role == self.Roles.Description:
+ return label.description
+ if role == Qt.ItemDataRole.DisplayRole:
+ return label.name
+ return None
+
+ def rowCount(self, parent: QModelIndex | QPersistentModelIndex = QModelIndex()) -> int:
+ if self._pull_request is None:
+ return 0
+ return len(self._pull_request.labels)
+
+ def roleNames(self) -> dict[int, QByteArray]:
+ return {
+ self.Roles.Name: QByteArray(b'name'),
+ self.Roles.LabelColor: QByteArray(b'labelColor'),
+ self.Roles.TextColor: QByteArray(b'textColor'),
+ self.Roles.Description: QByteArray(b'description'),
+ }
+
+ def text_color(self, background_color: str) -> str:
+ r = int(background_color[0:2], 16)
+ g = int(background_color[2:4], 16)
+ b = int(background_color[4:6], 16)
+ def _t(value: float) -> float:
+ if value <= 0.04045:
+ return value / 12.92
+ return ((value + 0.055) / 1.055) ** 2.4
+ srgb = [_t(value/255) for value in [r, g, b]]
+ L = 0.2126 * srgb[0] + 0.7152 * srgb[1] + 0.0722 * srgb[2];
+ if L > 0.179:
+ return '#000'
+ return '#fff'
diff --git a/kodereviewer/models/line_model.py b/kodereviewer/models/line_model.py
new file mode 100644
index 0000000..1814829
--- /dev/null
+++ b/kodereviewer/models/line_model.py
@@ -0,0 +1,81 @@
+"""Model used for line numbers in Editor.qml"""
+from enum import auto, IntEnum
+from typing import Optional
+
+from PySide6.QtCore import QAbstractListModel, QByteArray, QModelIndex, QObject, QPersistentModelIndex, QStandardPaths, QUrl, Qt, Signal, Slot, Property
+from PySide6.QtQuick import QQuickTextDocument
+from PySide6.QtQml import QmlElement
+
+QML_IMPORT_NAME = "org.deprecated.kodereviewer"
+QML_IMPORT_MAJOR_VERSION = 1
+
+
+@QmlElement
+class LineModel(QAbstractListModel):
+
+ _document: QQuickTextDocument | None
+
+ class Roles(IntEnum):
+ LineHeight = Qt.ItemDataRole.UserRole + 1
+
+ def __init__(self, *args, **kwargs):
+ self._document = None
+ super().__init__(*args, **kwargs)
+
+
+ def get_document(self):
+ return self._document
+
+ def set_document(self, document):
+ print('setting document')
+ if document == self._document:
+ return
+ self._document = document
+ self.documentChanged.emit()
+ self.resetModel()
+
+ documentChanged = Signal()
+ document = Property(QQuickTextDocument, fget=get_document, fset=set_document,
+ notify=documentChanged)
+
+ def data(self,
+ index: QModelIndex | QPersistentModelIndex,
+ role: int = Qt.ItemDataRole.DisplayRole) -> object:
+
+ if not index.isValid():
+ print('index not valid')
+ return
+
+ if self._document is None:
+ print('document none')
+ return
+
+ row = index.row()
+ if row < 0 or row > self.rowCount():
+ print(f'row: {row}')
+ return
+
+ if role == self.Roles.LineHeight:
+ text_doc = self._document.textDocument()
+ return int(text_doc.documentLayout().blockBoundingRect(text_doc.findBlockByNumber(row)).height())
+ print('found role ?')
+
+
+ def rowCount(self, parent: QModelIndex | QPersistentModelIndex = QModelIndex()) -> int:
+ if self._document is None:
+ print('document is none: row count 0')
+ return 0
+ print(f'returning {self._document.textDocument().blockCount()}')
+ return self._document.textDocument().blockCount();
+
+ def roleNames(self) -> dict[int, QByteArray]:
+ return {
+ self.Roles.LineHeight: QByteArray(b'lineHeight'),
+ }
+
+
+ @Slot()
+ def resetModel(self) -> None:
+ print('reseting model?')
+ self.beginResetModel()
+ self.endResetModel()
diff --git a/kodereviewer/qml/Editor.qml b/kodereviewer/qml/Editor.qml
new file mode 100644
index 0000000..9cc6cbf
--- /dev/null
+++ b/kodereviewer/qml/Editor.qml
@@ -0,0 +1,100 @@
+pragma ComponentBehavior: Bound
+import QtQuick 6.7
+import QtQuick.Controls 6.7 as QQC2
+import QtQuick.Layouts 6.7
+
+import org.kde.kirigami as Kirigami
+
+import org.kde.syntaxhighlighting
+
+import org.deprecated.kodereviewer
+
+TextEdit {
+
+ id: root
+
+ required property string file
+
+ signal commentClicked(string path, int startLine, int endLine)
+ signal newComment(string path, int startLine, int endLine)
+
+ property string filename: "A.patch"
+
+ property int lineHeight: contentHeight / lineCount
+
+ leftPadding: lineNumberColumn.width + lineNumberColumn.anchors.leftMargin + Kirigami.Units.smallSpacing * 2
+
+ readOnly: true
+ textFormat: TextEdit.PlainText
+ wrapMode: TextEdit.Wrap
+ color: Kirigami.Theme.textColor
+ selectionColor: Kirigami.Theme.highlightColor
+ font.family: "monospace"
+ Kirigami.SpellCheck.enabled: false
+
+ LineModel {
+ id: lineModel
+ document: root.textDocument
+ }
+
+ onWidthChanged: lineModel.resetModel()
+ onHeightChanged: lineModel.resetModel()
+
+ SyntaxHighlighter {
+ id: highlighter
+ textEdit: root
+ definition: Repository.definitionForFileName(root.filename)
+ }
+
+ Kirigami.Separator {
+ anchors {
+ top: root.top
+ bottom: root.bottom
+ left: lineNumberColumn.right
+ leftMargin: Kirigami.Units.smallSpacing
+ }
+ }
+
+ ColumnLayout {
+ id: lineNumberColumn
+ anchors {
+ top: root.top
+ topMargin: root.topPadding
+ left: root.left
+ leftMargin: Kirigami.Units.smallSpacing
+ }
+ spacing: 0
+ Repeater {
+ id: repeater
+ model: lineModel
+ delegate: QQC2.Label {
+ id: label
+ required property int index
+ required property int lineHeight
+ Layout.fillWidth: true
+ Layout.preferredHeight: lineHeight
+ horizontalAlignment: Text.AlignRight
+ text: index + 1
+ color: Kirigami.Theme.disabledTextColor
+
+ font.family: "monospace"
+ }
+ }
+ }
+
+ onTextChanged: print(root.textDocument)
+
+ onFileChanged: {
+ repeater.model.resetModel()
+ }
+
+ function selectionStartLine() {
+ const beforeText = text.toString().substr(0, selectionStart)
+ return beforeText.split("\n").length
+ }
+
+ function selectionEndLine() {
+ const beforeText = text.toString().substr(0, selectionEnd)
+ return beforeText.split("\n").length
+ }
+}
diff --git a/kodereviewer/qml/FilesView.qml b/kodereviewer/qml/FilesView.qml
new file mode 100644
index 0000000..299c353
--- /dev/null
+++ b/kodereviewer/qml/FilesView.qml
@@ -0,0 +1,61 @@
+pragma ComponentBehavior: Bound
+import QtQuick
+import QtCore
+import QtQuick.Controls as QQC2
+import QtQuick.Layouts
+
+import org.kde.kirigami as Kirigami
+import org.kde.kirigamiaddons.delegates as Delegates
+
+import org.deprecated.kodereviewer 1.0
+
+QQC2.SplitView {
+ id: root
+ anchors.fill: parent
+ padding: 0
+ spacing: 0
+ ListModel {
+ id: fileModel
+ ListElement {
+ filename: "file1"
+ status: "added"
+ additions: 100
+ deletions: 40
+ patch: "@@ -132,7 +132,7 @@ module Test @@ -1000,7 +1000,7 @@ module Test"
+ }
+ ListElement {
+ filename: "file2"
+ status: "added"
+ additions: 100
+ deletions: 40
+ patch: "@@ -1,9 +1,9 @@ def something @@ -1000,7 +1000,7 @@ module Test"
+ }
+ }
+
+
+ QQC2.ScrollView {
+ SplitView.preferredWidth: 200
+ implicitWidth: 200
+ SplitView.maximumWidth: 400
+ ListView {
+ model: fileModel
+ delegate: Delegates.RoundedItemDelegate {
+ required property string filename
+ required property string patch
+ highlighted: ListView.isCurrentItem
+ text: filename
+
+ onClicked: {
+ textArea.text = patch
+ textArea.file = filename
+ }
+ }
+ }
+ }
+
+ Editor {
+ id: textArea
+ text: ""
+ file: ""
+ }
+}
diff --git a/kodereviewer/qml/MarkdownTextArea.qml b/kodereviewer/qml/MarkdownTextArea.qml
new file mode 100644
index 0000000..966b209
--- /dev/null
+++ b/kodereviewer/qml/MarkdownTextArea.qml
@@ -0,0 +1,20 @@
+import QtQml
+import QtQuick 6.7
+import QtQuick.Controls 6.7 as QQC2
+import org.kde.kirigami as Kirigami
+import org.kde.syntaxhighlighting 1
+
+QQC2.TextArea {
+ id: root
+
+ placeholderText: "Leave a comment"
+ wrapMode: TextEdit.Wrap
+
+ Kirigami.SpellCheck.enabled: true
+ SyntaxHighlighter {
+ id: hightlighter
+ textEdit: root
+ repository: Repository
+ definition: Repository.definitionForFileName("la.md")
+ }
+}