use ratatui::crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
use vissue_core::keys::{ActionId, KeyMap};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Action {
Continue,
Quit,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Pane {
Ready,
List,
Claims,
Agenda,
Search,
}
impl Pane {
pub const ALL: [Pane; 5] = [
Pane::Ready,
Pane::List,
Pane::Claims,
Pane::Agenda,
Pane::Search,
];
pub fn title(self) -> &'static str {
match self {
Self::Ready => "Ready",
Self::List => "List",
Self::Claims => "Claims",
Self::Agenda => "Agenda",
Self::Search => "Search",
}
}
pub fn index(self) -> usize {
Self::ALL.iter().position(|p| *p == self).unwrap_or(0)
}
pub fn from_index(i: usize) -> Self {
Self::ALL[i % Self::ALL.len()]
}
pub fn next(self) -> Self {
Self::from_index(self.index() + 1)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DetailTab {
Show,
Excerpt,
Tree,
Related,
Recall,
}
impl DetailTab {
pub const ALL: [DetailTab; 5] = [
DetailTab::Show,
DetailTab::Excerpt,
DetailTab::Tree,
DetailTab::Related,
DetailTab::Recall,
];
pub fn title(self) -> &'static str {
match self {
Self::Show => "show",
Self::Excerpt => "excerpt",
Self::Tree => "tree",
Self::Related => "related",
Self::Recall => "recall",
}
}
pub fn next(self) -> Self {
let i = Self::ALL.iter().position(|t| *t == self).unwrap_or(0);
Self::ALL[(i + 1) % Self::ALL.len()]
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Focus {
Rows,
Detail,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PromptKind {
Search,
Note,
Deed,
Project,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConfirmKind {
Done,
Cancelled,
}
impl ConfirmKind {
pub fn state(self) -> &'static str {
match self {
Self::Done => "DONE",
Self::Cancelled => "CANCELLED",
}
}
}
pub fn is_press(key: KeyEvent) -> bool {
key.kind == KeyEventKind::Press || key.kind == KeyEventKind::Repeat
}
pub fn char_of(key: KeyEvent) -> Option<char> {
match key.code {
KeyCode::Char(c) if key.modifiers.is_empty() || key.modifiers == KeyModifiers::SHIFT => {
Some(c)
}
_ => None,
}
}
pub fn chord_of(key: KeyEvent) -> Option<String> {
match key.code {
KeyCode::Char(' ') => Some("space".to_string()),
KeyCode::Char(_) => char_of(key).map(vissue_core::keys::chord_from_char),
KeyCode::Enter => Some("enter".to_string()),
KeyCode::Esc => Some("esc".to_string()),
KeyCode::Tab => Some("tab".to_string()),
_ => None,
}
}
pub const TUI_ACTIONS: &[ActionId] = &[
ActionId::ListDown,
ActionId::ListUp,
ActionId::ListSelect,
ActionId::ListDone,
ActionId::PaneReady,
ActionId::PaneList,
ActionId::PaneClaims,
ActionId::PaneAgenda,
ActionId::PaneSearch,
ActionId::PaneNext,
ActionId::DetailCycle,
ActionId::ProjectCycle,
ActionId::Search,
ActionId::Claim,
ActionId::Note,
ActionId::Deed,
ActionId::StateCycle,
ActionId::ConfirmDone,
ActionId::ConfirmCancel,
ActionId::Open,
ActionId::CopyId,
ActionId::Reload,
ActionId::Help,
];
pub fn help_text(keymap: &KeyMap) -> String {
let mut out = String::from("vissue tui\n\n");
for row in KeyMap::catalog() {
if !TUI_ACTIONS.contains(&row.id) {
continue;
}
out.push_str(&format!(
"{:<13} {}\n",
keymap.chord_for(row.id),
row.id.title()
));
}
out.push_str("arrows move\nq / Esc quit / back\n");
let hud_only: Vec<String> = KeyMap::catalog()
.iter()
.filter(|row| !TUI_ACTIONS.contains(&row.id))
.map(|row| format!("{} {}", keymap.chord_for(row.id), row.id.title()))
.collect();
if !hud_only.is_empty() {
out.push_str(&format!("\nHUD only: {}\n", hud_only.join(", ")));
}
out.push_str("\nBody edits stay in the file.\nbody lives in file; open the range above\n");
out
}