From 9157444f4e0bc50e16e6bd13c3e7284104e1d2e8 Mon Sep 17 00:00:00 2001 From: Matias Linares Date: Mon, 14 Oct 2024 07:11:33 -0300 Subject: Add context drawer --- kodereviewer/models/__init__.py | 3 +- kodereviewer/models/file.py | 180 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 180 insertions(+), 3 deletions(-) (limited to 'kodereviewer/models') diff --git a/kodereviewer/models/__init__.py b/kodereviewer/models/__init__.py index b9082d1..ad675bf 100644 --- a/kodereviewer/models/__init__.py +++ b/kodereviewer/models/__init__.py @@ -1,5 +1,5 @@ from kodereviewer.models.comments import CommentModel -from kodereviewer.models.file import FileModel +from kodereviewer.models.file import FileModel, TreeFileModel from kodereviewer.models.line_model import LineModel from kodereviewer.models.project import ProjectModel from kodereviewer.models.pull_request import PullRequestModel @@ -8,6 +8,7 @@ from kodereviewer.models.label import LabelModel __all__ = [ 'CommentModel', 'FileModel', + 'TreeFileModel', 'LabelModel' 'LineModel', 'ProjectModel', diff --git a/kodereviewer/models/file.py b/kodereviewer/models/file.py index 246bd5b..4f0d139 100644 --- a/kodereviewer/models/file.py +++ b/kodereviewer/models/file.py @@ -1,8 +1,14 @@ +import logging from copy import copy +from dataclasses import dataclass, field from enum import IntEnum, auto -from typing import Any, Optional +from pathlib import Path +from typing import Any, Optional, Self -from PySide6.QtCore import QAbstractListModel, QByteArray, QModelIndex, QObject, QPersistentModelIndex, QStandardPaths, QUrl, Qt, Signal, Slot, Property +from PySide6.QtCore import ( + QAbstractListModel, QAbstractItemModel, QByteArray, QModelIndex, QObject, + QPersistentModelIndex, QStandardPaths, QUrl, Qt, Signal, Slot, Property +) from PySide6.QtQml import QmlElement from kodereviewer.project import Project @@ -11,6 +17,176 @@ from kodereviewer.data import ChangedFile, Label, PullRequest QML_IMPORT_NAME = "org.deprecated.kodereviewer" QML_IMPORT_MAJOR_VERSION = 1 +logger = logging.getLogger(__name__) + + +@dataclass +class FileItem: + filename: str + parent: Self | None + children: list[Self] = field(default_factory=list) + + def append(self, child: Self): + self.children.append(child) + + def child(self, row: int) -> Self | None: + if row >= 0 and row < len(self.children): + return self.children[row] + return None + + def row(self) -> int: + """Reports the item's location within it's parent.""" + if self.parent is None: + return 0 + for i, child in enumerate(self.parent.children): + if self is child: + return i + + raise Exception('Should not happen!') + + def column_count(self) -> int: + """Only the filename.""" + return 1 + + def __str__(self) -> str: + return f'{self.filename} | {id(self.parent)} | {[x.filename for x in self.children]}' + + +@QmlElement +class TreeFileModel(QAbstractItemModel): + + filenames: list[str] = field(default_factory=list) + dir_mapping: dict[str, FileItem] = field(default_factory=dict) + root_node: FileItem + _pull_request: Optional[PullRequest] + + def __init__(self): + super().__init__() + self.root_node = FileItem('./', None) + self._pull_request = None + + def load_files(self, filenames: list[str]): + logger.info(f'Loading {filenames}') + self.filenames = filenames + self.dir_mapping: dict[str, FileItem] = {} + root_node = FileItem('./', None) + for file in self.filenames: + p = Path(file) + directories = p.parts[:-1] + fname = p.name + current_path = Path('') + parent: FileItem = root_node + for dir in directories: + current_path = current_path / dir + if str(current_path) not in self.dir_mapping: + logger.info(f'Creating {current_path}') + self.dir_mapping[str(current_path)] = FileItem( + str(current_path), parent + ) + logger.info(f'Appending to {parent}') + parent.append(self.dir_mapping[str(current_path)]) + + parent = self.dir_mapping[str(current_path)] + file_item = FileItem(fname, parent) + parent.append(file_item) + self.root_node = root_node + logger.info(self.root_node) + + 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._pull_request.filesLoaded.connect(self._reset_model) + self.endResetModel() + self.pullRequestChanged.emit() + + pullRequest = Property(PullRequest, fget=get_pull_request, fset=set_pull_request, + notify=pullRequestChanged) + + def _reset_model(self) -> None: + logger.info('reseting model') + self.beginResetModel() + filenames = [f.filename for f in self._pull_request.files] + self.load_files(filenames) + self.endResetModel() + + def index(self, row: int, column: int, + index: QModelIndex = QModelIndex()) -> QModelIndex: + """Returns the index of the item in the model specified by the given row, column and parent index.""" + if not self.hasIndex(row, column, index): + return QModelIndex() + item: FileItem + if not index.isValid(): + item = self.root_node + else: + item = index.internalPointer() + + if item.child(row): + return self.createIndex(row, column, item.child(row)) + return QModelIndex() + + def parent(self, index: QModelIndex) -> QModelIndex: + """Returns the parent of the model item with the given index + + If the item has no parent, an invalid QModelIndex is returned. + """ + if not index.isValid(): + return QModelIndex() + + item: FileItem = index.internalPointer() + parent: FileItem = item.parent + if parent == self.root_node: + return QModelIndex() + return self.createIndex(parent.row(), 0, parent) + + def rowCount(self, index: QModelIndex = QModelIndex()) -> int: + """Returns the number of rows under the given parent + + When the parent is valid it means that rowCount is returning + the number of children of parent. """ + + parent: FileItem + if index.isValid(): + parent = index.internalPointer() + else: + parent = self.root_node + + return len(parent.children) + + def columnCount(self, parent: QModelIndex = QModelIndex()) -> int: + return 1 + + FilenameRole = Qt.ItemDataRole.UserRole + 1 + + def data(self, index: QModelIndex, role: int) -> Any: + if not index.isValid(): + return '' + filename = index.internalPointer().filename + name = Path(filename).name + logger.info(index) + logger.info(f'Role: {role} | {self.FilenameRole}') + if role == Qt.ItemDataRole.DisplayRole: + return name + if role == self.FilenameRole: + logger.info(name) + return name + return None + + def roleNames(self) -> dict[int, QByteArray]: + return { + self.FilenameRole: QByteArray(b'filename'), + } + @QmlElement class FileModel(QAbstractListModel): -- cgit v1.2.3-70-g09d2