use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
use super::consts::UblxTabNumber;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum UblxAction {
Quit,
Help,
MainModeSnapshot,
MainModeDelta,
MainModeDuplicates,
MainModeSettings,
MainModeLenses,
MainModeToggle,
LoadDuplicates,
SearchStart,
SearchChar(char),
SearchBackspace,
SearchSubmit,
SearchClear,
ViewerFindOpen,
ViewerFindChar(char),
ViewerFindBackspace,
ViewerFindSubmit,
ViewerFindClear,
ViewerFindNext,
ViewerFindPrev,
CycleRightPane,
RightPaneViewer,
ViewerFullscreenToggle,
RightPaneTemplates,
RightPaneMetadata,
RightPaneWriting,
ScrollPreviewUp,
ScrollPreviewDown,
ListTop,
ListBottom,
PreviewTop,
PreviewBottom,
MoveUp,
MoveDown,
MoveUpFast,
MoveDownFast,
FocusCategories,
FocusContents,
Tab,
TakeSnapshot,
CycleContentSort,
ThemeSelector,
OpenConfigInEditor,
ReloadConfig,
ExportZahirJson,
ExportLensMarkdown,
MultiselectToggleRow,
MultiselectOpenBulkMenu,
AddToOtherLens,
BulkMenuHotkeySelect(usize),
MultiselectCancel,
SpaceMenu,
SpaceMenuHotkeySelect(usize),
ConfirmYes,
ConfirmNo,
Noop,
}
pub struct KeyActionResult {
pub action: UblxAction,
pub last_key_for_double: Option<char>,
}
#[derive(Clone, Copy, Debug)]
pub struct KeySearchState {
pub active: bool,
pub has_filter: bool,
}
#[derive(Clone, Copy, Debug)]
pub struct KeyOptionalTabs {
pub duplicates: bool,
pub lenses: bool,
}
#[derive(Clone, Copy, Debug)]
pub struct ViewerFinderBools {
pub typing: bool,
pub committed: bool,
}
#[derive(Clone, Copy, Debug)]
pub struct MultiselectBools {
pub active: bool,
pub bulk_menu_visible: bool,
pub block_bulk_activation: bool,
}
#[derive(Clone, Copy, Debug)]
pub struct AllowBools {
pub viewer_find: bool,
pub lens_add_to_other_hotkey: bool,
}
#[derive(Clone, Copy, Debug)]
pub struct KeyActionContext {
pub search: KeySearchState,
pub viewer_find: ViewerFinderBools,
pub allow: AllowBools,
pub last_key_for_double: Option<char>,
pub tabs: KeyOptionalTabs,
pub tab_keys: UblxTabNumber,
pub multiselect: MultiselectBools,
pub panel_focus_contents: bool,
pub lens_menu_list_open: bool,
}
#[must_use]
fn main_mode_action_for_digit(
c: char,
keys: UblxTabNumber,
tabs: KeyOptionalTabs,
) -> Option<UblxAction> {
let ch = |n: u8| char::from_digit(u32::from(n), 10);
if ch(keys.snapshot) == Some(c) {
return Some(UblxAction::MainModeSnapshot);
}
if ch(keys.delta) == Some(c) {
return Some(UblxAction::MainModeDelta);
}
if ch(keys.settings) == Some(c) {
return Some(UblxAction::MainModeSettings);
}
if tabs.lenses && ch(keys.lenses) == Some(c) {
return Some(UblxAction::MainModeLenses);
}
if tabs.duplicates && ch(keys.duplicates) == Some(c) {
return Some(UblxAction::MainModeDuplicates);
}
None
}
#[must_use]
fn viewer_find_typing_mapping(event: KeyEvent) -> KeyActionResult {
let (action, last_key) = match event.code {
KeyCode::Esc => (UblxAction::ViewerFindClear, None),
KeyCode::Enter => (UblxAction::ViewerFindSubmit, None),
KeyCode::Backspace => (UblxAction::ViewerFindBackspace, None),
KeyCode::Char(c) => (UblxAction::ViewerFindChar(c), None),
_ => (UblxAction::Noop, None),
};
KeyActionResult {
action,
last_key_for_double: last_key,
}
}
#[must_use]
fn try_esc_special(ctx: &KeyActionContext) -> Option<(UblxAction, Option<char>)> {
if ctx.viewer_find.committed {
return Some((UblxAction::ViewerFindClear, None));
}
if ctx.search.active || ctx.search.has_filter {
return Some((UblxAction::SearchClear, None));
}
if ctx.multiselect.bulk_menu_visible || ctx.lens_menu_list_open {
return Some((UblxAction::SearchClear, None));
}
if ctx.multiselect.active && !ctx.search.active {
return Some((UblxAction::MultiselectCancel, None));
}
None
}
#[must_use]
fn try_char_action_modifiers(
code: KeyCode,
shift: bool,
ctrl: bool,
ctx: &KeyActionContext,
) -> Option<(UblxAction, Option<char>)> {
match code {
KeyCode::Char('s' | 'S') if shift && ctx.allow.viewer_find && !ctx.search.active => {
Some((UblxAction::ViewerFindOpen, None))
}
KeyCode::Char('n')
if !shift
&& !ctrl
&& ctx.viewer_find.committed
&& !ctx.viewer_find.typing
&& !ctx.search.active =>
{
Some((UblxAction::ViewerFindNext, None))
}
KeyCode::Char('N')
if !ctrl
&& ctx.viewer_find.committed
&& !ctx.viewer_find.typing
&& !ctx.search.active =>
{
Some((UblxAction::ViewerFindPrev, None))
}
KeyCode::Char('f' | 'F') if shift => Some((UblxAction::ViewerFullscreenToggle, None)),
KeyCode::Char('J') | KeyCode::Down if shift => Some((UblxAction::ScrollPreviewDown, None)),
KeyCode::Char('K') | KeyCode::Up if shift => Some((UblxAction::ScrollPreviewUp, None)),
KeyCode::Char('B') if shift => Some((UblxAction::PreviewTop, None)),
KeyCode::Char('E') if shift => Some((UblxAction::PreviewBottom, None)),
KeyCode::Char('j' | 'J') | KeyCode::Down if ctrl => Some((UblxAction::MoveDownFast, None)),
KeyCode::Char('k' | 'K') | KeyCode::Up if ctrl => Some((UblxAction::MoveUpFast, None)),
KeyCode::Char('~') => Some((UblxAction::MainModeToggle, None)),
KeyCode::Char('\u{60}') if shift && !ctx.search.active => {
Some((UblxAction::MainModeToggle, None))
}
KeyCode::Char('G') if shift => Some((UblxAction::ListBottom, None)),
KeyCode::Char('g') if !shift && !ctrl => {
if ctx.last_key_for_double == Some('g') {
Some((UblxAction::ListTop, None))
} else {
Some((UblxAction::Noop, Some('g')))
}
}
_ => None,
}
}
#[must_use]
fn key_action_multiselect_lens_or_digit(c: char, ctx: &KeyActionContext) -> Option<UblxAction> {
if ctx.multiselect.active
&& ctx.panel_focus_contents
&& !ctx.search.active
&& !ctx.multiselect.bulk_menu_visible
&& !ctx.multiselect.block_bulk_activation
&& c == 'a'
{
return Some(UblxAction::MultiselectOpenBulkMenu);
}
if ctx.allow.lens_add_to_other_hotkey && c == 'a' {
return Some(UblxAction::AddToOtherLens);
}
if ctx.multiselect.active
&& ctx.panel_focus_contents
&& !ctx.search.active
&& !ctx.multiselect.bulk_menu_visible
&& !ctx.multiselect.block_bulk_activation
&& c == ' '
{
return Some(UblxAction::MultiselectToggleRow);
}
main_mode_action_for_digit(c, ctx.tab_keys, ctx.tabs)
}
#[must_use]
fn key_action_navigation_letter(c: char) -> UblxAction {
match c {
' ' => UblxAction::SpaceMenu,
'e' => UblxAction::OpenConfigInEditor,
'v' => UblxAction::RightPaneViewer,
't' => UblxAction::RightPaneTemplates,
'm' => UblxAction::RightPaneMetadata,
'w' => UblxAction::RightPaneWriting,
's' => UblxAction::CycleContentSort,
'j' => UblxAction::MoveDown,
'k' => UblxAction::MoveUp,
'h' => UblxAction::FocusCategories,
'l' => UblxAction::FocusContents,
'J' => UblxAction::ScrollPreviewDown,
'K' => UblxAction::ScrollPreviewUp,
_ => UblxAction::Noop,
}
}
#[must_use]
fn key_action_plain_char(c: char, ctx: &KeyActionContext) -> (UblxAction, Option<char>) {
if let Some(a) = key_action_multiselect_lens_or_digit(c, ctx) {
return (a, None);
}
(key_action_navigation_letter(c), None)
}
#[must_use]
fn key_action_default(event: KeyEvent, ctx: &KeyActionContext) -> KeyActionResult {
let shift = event.modifiers.contains(KeyModifiers::SHIFT);
let ctrl = event.modifiers.contains(KeyModifiers::CONTROL);
let (action, last_key) = match event.code {
KeyCode::Esc => try_esc_special(ctx).unwrap_or((UblxAction::Quit, None)),
KeyCode::Char('q') => (UblxAction::Quit, None),
KeyCode::Char('?') => (UblxAction::Help, None),
KeyCode::Char('/') if !ctx.search.active => (UblxAction::SearchStart, None),
KeyCode::Char(c) if ctx.search.active => (UblxAction::SearchChar(c), None),
code => {
if let Some(t) = try_char_action_modifiers(code, shift, ctrl, ctx) {
t
} else {
match code {
KeyCode::Char(c) if shift => (UblxAction::SearchChar(c), None),
KeyCode::Char(c) if !ctrl => key_action_plain_char(c, ctx),
KeyCode::Enter => (UblxAction::SearchSubmit, None),
KeyCode::Backspace => (UblxAction::SearchBackspace, None),
KeyCode::Up => (UblxAction::MoveUp, None),
KeyCode::Down => (UblxAction::MoveDown, None),
KeyCode::Left => (UblxAction::FocusCategories, None),
KeyCode::Right => (UblxAction::FocusContents, None),
KeyCode::Tab => (UblxAction::Tab, None),
KeyCode::BackTab => (UblxAction::CycleRightPane, None),
_ => (UblxAction::Noop, None),
}
}
}
};
KeyActionResult {
action,
last_key_for_double: last_key,
}
}
#[must_use]
pub fn key_action_setup(event: KeyEvent, ctx: &KeyActionContext) -> KeyActionResult {
if event.kind != KeyEventKind::Press {
return KeyActionResult {
action: UblxAction::Noop,
last_key_for_double: None,
};
}
if ctx.viewer_find.typing {
let r = viewer_find_typing_mapping(event);
if !matches!(r.action, UblxAction::Noop) {
return r;
}
}
key_action_default(event, ctx)
}
#[must_use]
pub fn search_consumes(action: UblxAction) -> bool {
matches!(
action,
UblxAction::SearchClear
| UblxAction::SearchSubmit
| UblxAction::SearchBackspace
| UblxAction::SearchChar(_)
)
}
#[must_use]
pub fn viewer_find_consumes(action: UblxAction) -> bool {
matches!(
action,
UblxAction::ViewerFindClear
| UblxAction::ViewerFindSubmit
| UblxAction::ViewerFindBackspace
| UblxAction::ViewerFindChar(_)
)
}