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
"""Table of PyGitWeb projects under PROJECTROOT."""
from __future__ import annotations
from pyratatui import Constraint, Direction, Frame, Layout, List, ListItem, ListState, Paragraph, Rect
from pygittools.tui.git_colors import current_theme, focus_style
from pygittools.tui.keys import KeyLike, is_down, is_enter, is_esc, is_quit, is_up
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
class ProjectsPage:
title = "projects"
def __init__(self) -> None:
self._projects: list[ProjectEntry] = []
self._projectroot_label = ""
self._list_state = ListState()
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._list_state.select(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, frame: Frame, area: Rect) -> None:
if area.height <= 0:
return
theme = current_theme()
chunks = (
Layout()
.direction(Direction.Vertical)
.constraints([Constraint.length(1), Constraint.length(1), Constraint.fill(1)])
.split(area)
)
title = f"Projects ({self._projectroot_label})"
frame.render_widget(Paragraph.from_string(title).style(theme.header), chunks[0])
name_width = max(8, chunks[1].width - _BRANCH_WIDTH - 2)
header = f"{'Name':<{name_width}} {'Branch':<{_BRANCH_WIDTH}}"
frame.render_widget(Paragraph.from_string(header).style(theme.header), chunks[1])
items: list[ListItem] = []
selected = self._list_state.selected or 0
for index, project in enumerate(self._projects):
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}}"
style = focus_style(theme.normal) if index == selected else theme.normal
items.append(ListItem(line, style))
if not items:
items.append(ListItem("No projects", theme.log_date))
frame.render_stateful_list(
List(items).highlight_style(theme.focus).highlight_symbol(""),
chunks[2],
self._list_state,
)
def handle_key(self, key: KeyLike) -> PageResult:
if is_quit(key) or is_esc(key):
return PageResult(action=PageAction.POP)
if self._error is not None:
self._error = None
if not self._projects:
return PageResult()
if is_up(key):
selected = self._list_state.selected or 0
self._list_state.select(max(0, selected - 1))
return PageResult()
if is_down(key):
selected = self._list_state.selected or 0
self._list_state.select(min(len(self._projects) - 1, selected + 1))
return PageResult()
if is_enter(key):
project = self._projects[self._list_state.selected or 0]
return PageResult(action=PageAction.POP, switch_repo=project.worktree)
return PageResult()
def projects_page() -> ProjectsPage:
return ProjectsPage()