1use ratatui::crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
4use vissue_core::keys::{ActionId, KeyMap};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum Action {
9 Continue,
11 Quit,
13}
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum Pane {
18 Ready,
20 List,
22 Claims,
24 Agenda,
26 Search,
28}
29
30impl Pane {
31 pub const ALL: [Pane; 5] = [
33 Pane::Ready,
34 Pane::List,
35 Pane::Claims,
36 Pane::Agenda,
37 Pane::Search,
38 ];
39
40 pub fn title(self) -> &'static str {
42 match self {
43 Self::Ready => "Ready",
44 Self::List => "List",
45 Self::Claims => "Claims",
46 Self::Agenda => "Agenda",
47 Self::Search => "Search",
48 }
49 }
50
51 pub fn index(self) -> usize {
53 Self::ALL.iter().position(|p| *p == self).unwrap_or(0)
54 }
55
56 pub fn from_index(i: usize) -> Self {
58 Self::ALL[i % Self::ALL.len()]
59 }
60
61 pub fn next(self) -> Self {
63 Self::from_index(self.index() + 1)
64 }
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub enum DetailTab {
70 Show,
72 Excerpt,
74 Tree,
76 Related,
78 Recall,
80}
81
82impl DetailTab {
83 pub const ALL: [DetailTab; 5] = [
85 DetailTab::Show,
86 DetailTab::Excerpt,
87 DetailTab::Tree,
88 DetailTab::Related,
89 DetailTab::Recall,
90 ];
91
92 pub fn title(self) -> &'static str {
94 match self {
95 Self::Show => "show",
96 Self::Excerpt => "excerpt",
97 Self::Tree => "tree",
98 Self::Related => "related",
99 Self::Recall => "recall",
100 }
101 }
102
103 pub fn next(self) -> Self {
105 let i = Self::ALL.iter().position(|t| *t == self).unwrap_or(0);
106 Self::ALL[(i + 1) % Self::ALL.len()]
107 }
108}
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq)]
112pub enum Focus {
113 Rows,
115 Detail,
117}
118
119#[derive(Debug, Clone, Copy, PartialEq, Eq)]
121pub enum PromptKind {
122 Search,
124 Note,
126 Deed,
128 Project,
130}
131
132#[derive(Debug, Clone, Copy, PartialEq, Eq)]
134pub enum ConfirmKind {
135 Done,
137 Cancelled,
139}
140
141impl ConfirmKind {
142 pub fn state(self) -> &'static str {
144 match self {
145 Self::Done => "DONE",
146 Self::Cancelled => "CANCELLED",
147 }
148 }
149}
150
151pub fn is_press(key: KeyEvent) -> bool {
153 key.kind == KeyEventKind::Press || key.kind == KeyEventKind::Repeat
154}
155
156pub fn char_of(key: KeyEvent) -> Option<char> {
158 match key.code {
159 KeyCode::Char(c) if key.modifiers.is_empty() || key.modifiers == KeyModifiers::SHIFT => {
160 Some(c)
161 }
162 _ => None,
163 }
164}
165
166pub fn chord_of(key: KeyEvent) -> Option<String> {
171 match key.code {
172 KeyCode::Char(' ') => Some("space".to_string()),
173 KeyCode::Char(_) => char_of(key).map(vissue_core::keys::chord_from_char),
174 KeyCode::Enter => Some("enter".to_string()),
175 KeyCode::Esc => Some("esc".to_string()),
176 KeyCode::Tab => Some("tab".to_string()),
177 _ => None,
178 }
179}
180
181pub const TUI_ACTIONS: &[ActionId] = &[
183 ActionId::ListDown,
184 ActionId::ListUp,
185 ActionId::ListSelect,
186 ActionId::ListDone,
187 ActionId::PaneReady,
188 ActionId::PaneList,
189 ActionId::PaneClaims,
190 ActionId::PaneAgenda,
191 ActionId::PaneSearch,
192 ActionId::PaneNext,
193 ActionId::DetailCycle,
194 ActionId::ProjectCycle,
195 ActionId::Search,
196 ActionId::Claim,
197 ActionId::Note,
198 ActionId::Deed,
199 ActionId::StateCycle,
200 ActionId::ConfirmDone,
201 ActionId::ConfirmCancel,
202 ActionId::Open,
203 ActionId::CopyId,
204 ActionId::Reload,
205 ActionId::Help,
206];
207
208pub fn help_text(keymap: &KeyMap) -> String {
211 let mut out = String::from("vissue tui\n\n");
212 for row in KeyMap::catalog() {
213 if !TUI_ACTIONS.contains(&row.id) {
214 continue;
215 }
216 out.push_str(&format!(
217 "{:<13} {}\n",
218 keymap.chord_for(row.id),
219 row.id.title()
220 ));
221 }
222 out.push_str("arrows move\nq / Esc quit / back\n");
223 let hud_only: Vec<String> = KeyMap::catalog()
224 .iter()
225 .filter(|row| !TUI_ACTIONS.contains(&row.id))
226 .map(|row| format!("{} {}", keymap.chord_for(row.id), row.id.title()))
227 .collect();
228 if !hud_only.is_empty() {
229 out.push_str(&format!("\nHUD only: {}\n", hud_only.join(", ")));
230 }
231 out.push_str("\nBody edits stay in the file.\nbody lives in file; open the range above\n");
232 out
233}