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
109
"""Keyboard input modes for TUI pages (navigation vs text entry)."""
from __future__ import annotations
from dataclasses import dataclass
from typing import Literal, Protocol
from unicurses import ( # type: ignore[import-untyped]
KEY_DOWN,
KEY_ENTER,
KEY_UP,
)
from pygittools.tui.changes_list import ChangesList
from pygittools.tui.pages.branches import branch_picker_page
from pygittools.tui.pages.help import help_page
from pygittools.tui.pages.projects import projects_page
from pygittools.tui.types import PageAction, PageContext, PageResult
_KEY_ESC = 27
_HeaderFocus = Literal["changes", "repo", "branch"]
@dataclass(frozen=True, slots=True)
class ContextDispatch:
page_result: PageResult | None = None
next_mode: Literal["nav", "input"] | None = None
class PageInputHost(Protocol):
@property
def header_focus(self) -> _HeaderFocus: ...
def set_header_focus(self, focus: _HeaderFocus) -> None: ...
@property
def page_context(self) -> PageContext | None: ...
@property
def changes(self) -> ChangesList: ...
class NavContext:
def status_hint(self, host: PageInputHost) -> str:
if host.header_focus == "repo":
return "Enter browse projects — j/k move"
if host.header_focus == "branch":
return "Enter switch branch — j/k move"
return host.changes.status_hint()
def dispatch_key(self, host: PageInputHost, key: int) -> ContextDispatch:
if key in (ord("h"), ord("H")):
return ContextDispatch(page_result=PageResult(action=PageAction.PUSH, next_page=help_page()))
if host.header_focus == "repo":
if key in (KEY_DOWN, ord("j"), ord("J")):
host.set_header_focus("branch")
elif key in (KEY_ENTER, 10, 13):
return ContextDispatch(page_result=PageResult(action=PageAction.PUSH, next_page=projects_page()))
return ContextDispatch()
if host.header_focus == "branch":
if key in (KEY_UP, ord("k"), ord("K")):
host.set_header_focus("repo")
elif key in (KEY_DOWN, ord("j"), ord("J")):
host.set_header_focus("changes")
elif key in (KEY_ENTER, 10, 13):
return ContextDispatch(page_result=PageResult(action=PageAction.PUSH, next_page=branch_picker_page()))
return ContextDispatch()
if key in (KEY_UP, ord("k"), ord("K")) and host.changes.is_at_top():
host.set_header_focus("branch")
return ContextDispatch()
ctx = host.page_context
if ctx is None:
return ContextDispatch()
nav_result = host.changes.handle_nav_key(key, ctx.repo)
if nav_result.enter_input:
return ContextDispatch(next_mode="input")
return ContextDispatch()
class InputContext:
def status_hint(self, host: PageInputHost) -> str:
del host
return "Esc relinquish input — Enter commit"
def dispatch_key(self, host: PageInputHost, key: int) -> ContextDispatch:
if key == _KEY_ESC:
return ContextDispatch(next_mode="nav")
ctx = host.page_context
if ctx is not None:
result = host.changes.handle_input_key(key, ctx.repo)
if result.relinquish:
return ContextDispatch(next_mode="nav")
return ContextDispatch()
InputMode = NavContext | InputContext
def nav_context() -> NavContext:
return NavContext()
def input_context() -> InputContext:
return InputContext()