vibefpmgui.file_diff
A file's diff with a line over it saying what's compared ("staged → working tree", "3f9db510 → 930ec19c"): the Git panel's Diff page and its History's. The two versions are read, and compared, on a pool thread; a file chosen meanwhile wins (the older answer is dropped).
1"""A file's diff with a line over it saying what's compared ("staged → 2working tree", "3f9db510 → 930ec19c"): the Git panel's Diff page and its 3History's. The two versions are read, and compared, on a pool thread; a 4file chosen meanwhile wins (the older answer is dropped). 5""" 6 7from __future__ import annotations 8 9from collections.abc import Callable, Hashable 10 11from qtpy.QtCore import Signal 12from qtpy.QtGui import QFont 13from qtpy.QtWidgets import QVBoxLayout, QWidget 14 15from .background import call_in_background 16from .diff_view import DiffView, compare 17from .git_status import BINARY 18from .widgets import StatusLabel 19 20# What load() reads: (the older text, the newer one, what's compared). A text 21# is None where there's no such version (added, deleted), BINARY where it 22# isn't text. It raises OSError, or RuntimeError (git_status.GitError), if 23# it can't be read. 24Versions = tuple[str | None, str | None, str] 25 26 27class FileDiff(QWidget): 28 open_requested = Signal(int) # a line of the newer version (1-based), double-clicked 29 30 def __init__(self, parent=None): 31 super().__init__(parent) 32 self.title = StatusLabel() 33 self.diff = DiffView() 34 self.diff.open_requested.connect(self.open_requested) 35 self._loading: Hashable | None = None # (what's being read: see load) 36 layout = QVBoxLayout(self) 37 layout.setContentsMargins(0, 0, 0, 0) 38 layout.setSpacing(4) 39 layout.addWidget(self.title) 40 layout.addWidget(self.diff, 1) 41 42 def load(self, key: Hashable, path: str, read: Callable[[], Versions]): 43 """Show ``path``'s diff, its versions from ``read()`` (on a pool 44 thread); ``key`` tells this file apart from the next one chosen.""" 45 self._loading = key 46 self.title.setText(f"<b>{path}</b> · reading…") 47 48 def work(): 49 try: 50 old, new, compared = read() 51 except (OSError, RuntimeError) as error: 52 return str(error) or type(error).__name__ 53 # (Compared here too: a third of a second for a big file, which the window shouldn't wait for.) 54 return compared, None if BINARY in (old, new) else compare(old, new) 55 56 call_in_background(work, lambda result: self._read(key, path, result), self) 57 58 def _read(self, key: Hashable, path: str, result): 59 if key != self._loading: 60 return # (another file chosen meanwhile) 61 if isinstance(result, str): 62 self.title.setText(f"<b>{path}</b> · couldn't read it: {result}") 63 self.diff.clear() 64 return 65 compared, rows = result 66 if rows is None: 67 self.title.setText(f"<b>{path}</b> · binary or very big: no diff shown") 68 self.diff.clear() 69 return 70 self.title.setText(f"<b>{path}</b> · {compared}") 71 self.diff.show_rows(rows, path) 72 73 def clear(self): 74 self._loading = None 75 self.title.setText("") 76 self.diff.clear() 77 78 def set_unified(self, on: bool): 79 self.diff.set_unified(on) 80 81 def set_font(self, font: QFont): 82 self.diff.set_font(font) 83 84 def refresh_style(self, muted: str): 85 """The colors: the title's (muted), the diff's (the syntax style's).""" 86 self.title.setStyleSheet(f"color: {muted};") 87 self.diff.refresh_style()
28class FileDiff(QWidget): 29 open_requested = Signal(int) # a line of the newer version (1-based), double-clicked 30 31 def __init__(self, parent=None): 32 super().__init__(parent) 33 self.title = StatusLabel() 34 self.diff = DiffView() 35 self.diff.open_requested.connect(self.open_requested) 36 self._loading: Hashable | None = None # (what's being read: see load) 37 layout = QVBoxLayout(self) 38 layout.setContentsMargins(0, 0, 0, 0) 39 layout.setSpacing(4) 40 layout.addWidget(self.title) 41 layout.addWidget(self.diff, 1) 42 43 def load(self, key: Hashable, path: str, read: Callable[[], Versions]): 44 """Show ``path``'s diff, its versions from ``read()`` (on a pool 45 thread); ``key`` tells this file apart from the next one chosen.""" 46 self._loading = key 47 self.title.setText(f"<b>{path}</b> · reading…") 48 49 def work(): 50 try: 51 old, new, compared = read() 52 except (OSError, RuntimeError) as error: 53 return str(error) or type(error).__name__ 54 # (Compared here too: a third of a second for a big file, which the window shouldn't wait for.) 55 return compared, None if BINARY in (old, new) else compare(old, new) 56 57 call_in_background(work, lambda result: self._read(key, path, result), self) 58 59 def _read(self, key: Hashable, path: str, result): 60 if key != self._loading: 61 return # (another file chosen meanwhile) 62 if isinstance(result, str): 63 self.title.setText(f"<b>{path}</b> · couldn't read it: {result}") 64 self.diff.clear() 65 return 66 compared, rows = result 67 if rows is None: 68 self.title.setText(f"<b>{path}</b> · binary or very big: no diff shown") 69 self.diff.clear() 70 return 71 self.title.setText(f"<b>{path}</b> · {compared}") 72 self.diff.show_rows(rows, path) 73 74 def clear(self): 75 self._loading = None 76 self.title.setText("") 77 self.diff.clear() 78 79 def set_unified(self, on: bool): 80 self.diff.set_unified(on) 81 82 def set_font(self, font: QFont): 83 self.diff.set_font(font) 84 85 def refresh_style(self, muted: str): 86 """The colors: the title's (muted), the diff's (the syntax style's).""" 87 self.title.setStyleSheet(f"color: {muted};") 88 self.diff.refresh_style()
QWidget(self, /, parent: PySide6.QtWidgets.QWidget | None = None, f: PySide6.QtCore.Qt.WindowType = Default(Qt.WindowFlags), *, modal: bool | None = None, windowModality: PySide6.QtCore.Qt.WindowModality | None = None, enabled: bool | None = None, geometry: PySide6.QtCore.QRect | None = None, frameGeometry: PySide6.QtCore.QRect | None = None, normalGeometry: PySide6.QtCore.QRect | None = None, x: int | None = None, y: int | None = None, pos: PySide6.QtCore.QPoint | None = None, frameSize: PySide6.QtCore.QSize | None = None, size: PySide6.QtCore.QSize | None = None, width: int | None = None, height: int | None = None, rect: PySide6.QtCore.QRect | None = None, childrenRect: PySide6.QtCore.QRect | None = None, childrenRegion: PySide6.QtGui.QRegion | None = None, sizePolicy: PySide6.QtWidgets.QSizePolicy | None = None, minimumSize: PySide6.QtCore.QSize | None = None, maximumSize: PySide6.QtCore.QSize | None = None, minimumWidth: int | None = None, minimumHeight: int | None = None, maximumWidth: int | None = None, maximumHeight: int | None = None, sizeIncrement: PySide6.QtCore.QSize | None = None, baseSize: PySide6.QtCore.QSize | None = None, palette: PySide6.QtGui.QPalette | None = None, font: PySide6.QtGui.QFont | None = None, cursor: PySide6.QtGui.QCursor | None = None, mouseTracking: bool | None = None, tabletTracking: bool | None = None, isActiveWindow: bool | None = None, focusPolicy: PySide6.QtCore.Qt.FocusPolicy | None = None, focus: bool | None = None, contextMenuPolicy: PySide6.QtCore.Qt.ContextMenuPolicy | None = None, updatesEnabled: bool | None = None, visible: bool | None = None, minimized: bool | None = None, maximized: bool | None = None, fullScreen: bool | None = None, sizeHint: PySide6.QtCore.QSize | None = None, minimumSizeHint: PySide6.QtCore.QSize | None = None, acceptDrops: bool | None = None, windowTitle: str | None = None, windowIcon: PySide6.QtGui.QIcon | None = None, windowIconText: str | None = None, windowOpacity: float | None = None, windowModified: bool | None = None, toolTip: str | None = None, toolTipDuration: int | None = None, statusTip: str | None = None, whatsThis: str | None = None, accessibleName: str | None = None, accessibleDescription: str | None = None, accessibleIdentifier: str | None = None, layoutDirection: PySide6.QtCore.Qt.LayoutDirection | None = None, autoFillBackground: bool | None = None, styleSheet: str | None = None, locale: PySide6.QtCore.QLocale | None = None, windowFilePath: str | None = None, inputMethodHints: PySide6.QtCore.Qt.InputMethodHint | None = None) -> None
31 def __init__(self, parent=None): 32 super().__init__(parent) 33 self.title = StatusLabel() 34 self.diff = DiffView() 35 self.diff.open_requested.connect(self.open_requested) 36 self._loading: Hashable | None = None # (what's being read: see load) 37 layout = QVBoxLayout(self) 38 layout.setContentsMargins(0, 0, 0, 0) 39 layout.setSpacing(4) 40 layout.addWidget(self.title) 41 layout.addWidget(self.diff, 1)
__init__(self, /, parent: PySide6.QtWidgets.QWidget | None = None, f: PySide6.QtCore.Qt.WindowType = Default(Qt.WindowFlags), *, modal: bool | None = None, windowModality: PySide6.QtCore.Qt.WindowModality | None = None, enabled: bool | None = None, geometry: PySide6.QtCore.QRect | None = None, frameGeometry: PySide6.QtCore.QRect | None = None, normalGeometry: PySide6.QtCore.QRect | None = None, x: int | None = None, y: int | None = None, pos: PySide6.QtCore.QPoint | None = None, frameSize: PySide6.QtCore.QSize | None = None, size: PySide6.QtCore.QSize | None = None, width: int | None = None, height: int | None = None, rect: PySide6.QtCore.QRect | None = None, childrenRect: PySide6.QtCore.QRect | None = None, childrenRegion: PySide6.QtGui.QRegion | None = None, sizePolicy: PySide6.QtWidgets.QSizePolicy | None = None, minimumSize: PySide6.QtCore.QSize | None = None, maximumSize: PySide6.QtCore.QSize | None = None, minimumWidth: int | None = None, minimumHeight: int | None = None, maximumWidth: int | None = None, maximumHeight: int | None = None, sizeIncrement: PySide6.QtCore.QSize | None = None, baseSize: PySide6.QtCore.QSize | None = None, palette: PySide6.QtGui.QPalette | None = None, font: PySide6.QtGui.QFont | None = None, cursor: PySide6.QtGui.QCursor | None = None, mouseTracking: bool | None = None, tabletTracking: bool | None = None, isActiveWindow: bool | None = None, focusPolicy: PySide6.QtCore.Qt.FocusPolicy | None = None, focus: bool | None = None, contextMenuPolicy: PySide6.QtCore.Qt.ContextMenuPolicy | None = None, updatesEnabled: bool | None = None, visible: bool | None = None, minimized: bool | None = None, maximized: bool | None = None, fullScreen: bool | None = None, sizeHint: PySide6.QtCore.QSize | None = None, minimumSizeHint: PySide6.QtCore.QSize | None = None, acceptDrops: bool | None = None, windowTitle: str | None = None, windowIcon: PySide6.QtGui.QIcon | None = None, windowIconText: str | None = None, windowOpacity: float | None = None, windowModified: bool | None = None, toolTip: str | None = None, toolTipDuration: int | None = None, statusTip: str | None = None, whatsThis: str | None = None, accessibleName: str | None = None, accessibleDescription: str | None = None, accessibleIdentifier: str | None = None, layoutDirection: PySide6.QtCore.Qt.LayoutDirection | None = None, autoFillBackground: bool | None = None, styleSheet: str | None = None, locale: PySide6.QtCore.QLocale | None = None, windowFilePath: str | None = None, inputMethodHints: PySide6.QtCore.Qt.InputMethodHint | None = None) -> None
Initialize self. See help(type(self)) for accurate signature.
43 def load(self, key: Hashable, path: str, read: Callable[[], Versions]): 44 """Show ``path``'s diff, its versions from ``read()`` (on a pool 45 thread); ``key`` tells this file apart from the next one chosen.""" 46 self._loading = key 47 self.title.setText(f"<b>{path}</b> · reading…") 48 49 def work(): 50 try: 51 old, new, compared = read() 52 except (OSError, RuntimeError) as error: 53 return str(error) or type(error).__name__ 54 # (Compared here too: a third of a second for a big file, which the window shouldn't wait for.) 55 return compared, None if BINARY in (old, new) else compare(old, new) 56 57 call_in_background(work, lambda result: self._read(key, path, result), self)
Show path's diff, its versions from read() (on a pool
thread); key tells this file apart from the next one chosen.