Skip to main content

vissue_tui/
keys.rs

1//! Key dispatch. Bindings are listed on `?`.
2
3use ratatui::crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
4
5/// What the event loop does after a key.
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum Action {
8    Continue,
9    Quit,
10}
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum Pane {
14    Ready,
15    List,
16    Claims,
17    Agenda,
18    Search,
19}
20
21impl Pane {
22    pub const ALL: [Pane; 5] = [
23        Pane::Ready,
24        Pane::List,
25        Pane::Claims,
26        Pane::Agenda,
27        Pane::Search,
28    ];
29
30    pub fn title(self) -> &'static str {
31        match self {
32            Self::Ready => "Ready",
33            Self::List => "List",
34            Self::Claims => "Claims",
35            Self::Agenda => "Agenda",
36            Self::Search => "Search",
37        }
38    }
39
40    pub fn index(self) -> usize {
41        Self::ALL.iter().position(|p| *p == self).unwrap_or(0)
42    }
43
44    pub fn from_index(i: usize) -> Self {
45        Self::ALL[i % Self::ALL.len()]
46    }
47
48    pub fn next(self) -> Self {
49        Self::from_index(self.index() + 1)
50    }
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub enum DetailTab {
55    Show,
56    Excerpt,
57    Tree,
58    Related,
59}
60
61impl DetailTab {
62    pub const ALL: [DetailTab; 4] = [
63        DetailTab::Show,
64        DetailTab::Excerpt,
65        DetailTab::Tree,
66        DetailTab::Related,
67    ];
68
69    pub fn title(self) -> &'static str {
70        match self {
71            Self::Show => "show",
72            Self::Excerpt => "excerpt",
73            Self::Tree => "tree",
74            Self::Related => "related",
75        }
76    }
77
78    pub fn next(self) -> Self {
79        let i = Self::ALL.iter().position(|t| *t == self).unwrap_or(0);
80        Self::ALL[(i + 1) % Self::ALL.len()]
81    }
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85pub enum Focus {
86    Rows,
87    Detail,
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq)]
91pub enum PromptKind {
92    Search,
93    Note,
94    Project,
95}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq)]
98pub enum ConfirmKind {
99    Done,
100    Cancelled,
101}
102
103impl ConfirmKind {
104    pub fn state(self) -> &'static str {
105        match self {
106            Self::Done => "DONE",
107            Self::Cancelled => "CANCELLED",
108        }
109    }
110}
111
112pub fn is_press(key: KeyEvent) -> bool {
113    key.kind == KeyEventKind::Press || key.kind == KeyEventKind::Repeat
114}
115
116pub fn char_of(key: KeyEvent) -> Option<char> {
117    match key.code {
118        KeyCode::Char(c) if key.modifiers.is_empty() || key.modifiers == KeyModifiers::SHIFT => {
119            Some(c)
120        }
121        _ => None,
122    }
123}
124
125pub const HELP: &str = "\
126vissue tui
127
128j/k, arrows   move
129Tab, 1-5      pane (Ready List Claims Agenda Search)
130Enter         focus detail / cycle detail tab
131p             project filter
132/             search
133c             claim
134n             note
135s             cycle TODO / STARTED / BLOCKED
136D             DONE (confirm)
137X             CANCELLED (confirm)
138o             open (shared selection)
139y             copy id
140R             reload
141?             this help
142q / Esc       quit / back
143
144Body edits stay in the file.
145body lives in file; open the range above
146";