1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
"""Table of PyGitWeb projects under PROJECTROOT."""
from __future__ import annotations
from pygittools.tui.ucurses import ( # type: ignore[import-untyped]
A_BOLD,
A_REVERSE,
KEY_DOWN,
KEY_ENTER,
KEY_RESIZE,
KEY_UP,
)
from pygittools.tui.draw import draw_line
from pygittools.tui.projects import ProjectEntry, list_projects
from pygittools.tui.pygitweb_layout import load_pygitweb_layout
from pygittools.tui.types import PageAction, PageContext, PageResult
_BRANCH_WIDTH = 16
_TABLE_TOP = 2
class ProjectsPage:
title = "projects"
def __init__(self) -> None:
self._projects: list[ProjectEntry] = []
self._projectroot_label = ""
self._cursor = 0
self._scroll_offset = 0
self._error: str | None = None
def on_enter(self, ctx: PageContext) -> None:
del ctx
layout = load_pygitweb_layout()
self._projectroot_label = str(layout.projectroot)
self._projects = list_projects(layout)
self._cursor = 0
self._scroll_offset = 0
self._error = None
def status_text(self) -> str:
if self._error:
return self._error
if not self._projects:
return "No projects found — Esc back"
return "Enter open project — j/k move — Esc back"
def draw(self, stdscr: int, height: int, width: int) -> None:
del stdscr
if height <= 0:
return
title = f"Projects ({self._projectroot_label})"
draw_line(0, 0, title[:width], width, A_BOLD)
name_width = max(8, width - _BRANCH_WIDTH - 2)
header = f"{'Name':<{name_width}} {'Branch':<{_BRANCH_WIDTH}}"
draw_line(1, 0, header[:width], width, A_BOLD)
list_height = height - _TABLE_TOP
if list_height <= 0:
return
self._ensure_cursor_visible(list_height)
for view_row in range(list_height):
index = self._scroll_offset + view_row
if index >= len(self._projects):
break
project = self._projects[index]
name = project.path
if len(name) > name_width:
name = name[: max(0, name_width - 1)] + "…"
branch = project.branch
if len(branch) > _BRANCH_WIDTH:
branch = branch[: max(0, _BRANCH_WIDTH - 1)] + "…"
line = f"{name:<{name_width}} {branch:<{_BRANCH_WIDTH}}"
attr = A_REVERSE if index == self._cursor else 0
draw_line(_TABLE_TOP + view_row, 0, line[:width], width, attr)
def handle_key(self, key: int) -> PageResult:
if key in (ord("q"), ord("Q"), 27):
return PageResult(action=PageAction.POP)
if key == KEY_RESIZE:
return PageResult()
if self._error is not None:
self._error = None
if not self._projects:
return PageResult()
if key in (KEY_UP, ord("k"), ord("K")):
self._cursor = max(0, self._cursor - 1)
return PageResult()
if key in (KEY_DOWN, ord("j"), ord("J")):
self._cursor = min(len(self._projects) - 1, self._cursor + 1)
return PageResult()
if key in (KEY_ENTER, 10, 13):
project = self._projects[self._cursor]
return PageResult(action=PageAction.POP, switch_repo=project.worktree)
return PageResult()
def _ensure_cursor_visible(self, list_height: int) -> None:
if self._cursor < self._scroll_offset:
self._scroll_offset = self._cursor
elif self._cursor >= self._scroll_offset + list_height:
self._scroll_offset = self._cursor - list_height + 1
def projects_page() -> ProjectsPage:
return ProjectsPage()