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
110
111
112
113
114
115
116
117
118
119
120
121
"""Form to add a new git worktree."""
from __future__ import annotations
from pathlib import Path
from typing import Literal
from pyratatui import (
Constraint,
Direction,
Frame,
Layout,
Paragraph,
Rect,
TextPrompt,
TextState,
)
from pygittools.tui.git_colors import current_theme
from pygittools.tui.git_worktrees import add_worktree
from pygittools.tui.keys import KeyLike, is_down, is_enter, is_esc, is_quit, is_up
from pygittools.tui.types import PageAction, PageContext, PageResult
_Field = Literal["path", "branch"]
class WorktreeAddPage:
title = "worktree-add"
def __init__(self) -> None:
self._ctx: PageContext | None = None
self._path_state = TextState()
self._branch_state = TextState()
self._field: _Field = "path"
self._error: str | None = None
def on_enter(self, ctx: PageContext) -> None:
self._ctx = ctx
self._path_state.set_value("")
self._branch_state.set_value("")
self._field = "path"
self._error = None
self._path_state.focus()
self._branch_state.blur()
def status_text(self) -> str:
if self._error:
return self._error
return "Enter submit — j/k switch field — 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.length(1),
Constraint.fill(1),
])
.split(area)
)
frame.render_widget(Paragraph.from_string("Add worktree").style(theme.header), chunks[0])
self._sync_focus()
frame.render_text_prompt(TextPrompt("Path: "), chunks[1], self._path_state)
frame.render_text_prompt(
TextPrompt("Branch (new or existing, empty = detached): "),
chunks[2],
self._branch_state,
)
hint = "Path is relative to the current worktree unless absolute."
frame.render_widget(Paragraph.from_string(hint).style(theme.log_date), chunks[3])
def handle_key(self, key: KeyLike) -> PageResult:
if is_esc(key) or is_quit(key):
return PageResult(action=PageAction.POP)
if self._error is not None:
self._error = None
ctx = self._ctx
if ctx is None:
return PageResult()
if is_up(key):
self._field = "path"
return PageResult()
if is_down(key):
self._field = "branch"
return PageResult()
if is_enter(key):
return self._submit(ctx)
state = self._path_state if self._field == "path" else self._branch_state
state.focus()
state.handle_key(key)
return PageResult()
def _sync_focus(self) -> None:
if self._field == "path":
self._path_state.focus()
self._branch_state.blur()
else:
self._branch_state.focus()
self._path_state.blur()
def _submit(self, ctx: PageContext) -> PageResult:
raw_path = self._path_state.value().strip()
if not raw_path:
self._error = "Path is required"
return PageResult()
branch = self._branch_state.value().strip() or None
ok, error = add_worktree(ctx.repo, Path(raw_path), branch)
if ok:
return PageResult(action=PageAction.POP, repo_changed=True)
self._error = error
return PageResult()
def worktree_add_page() -> WorktreeAddPage:
return WorktreeAddPage()