269 lines
10 KiB
Rust
269 lines
10 KiB
Rust
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
|
use std::sync::atomic::Ordering;
|
|
|
|
use super::{InputContext, InputResult};
|
|
use crate::commands::AppCommand;
|
|
use crate::model::categories;
|
|
use crate::model::docs;
|
|
use crate::state::{ConfirmAction, DictFocus, FlashKind, HelpFocus, Modal};
|
|
|
|
pub(super) fn handle_help_page(ctx: &mut InputContext, key: KeyEvent) -> InputResult {
|
|
let ctrl = key.modifiers.contains(KeyModifiers::CONTROL);
|
|
|
|
if ctx.app.ui.help_search_active {
|
|
match key.code {
|
|
KeyCode::Esc => ctx.dispatch(AppCommand::HelpClearSearch),
|
|
KeyCode::Enter => ctx.dispatch(AppCommand::HelpSearchConfirm),
|
|
KeyCode::Backspace => ctx.dispatch(AppCommand::HelpSearchBackspace),
|
|
KeyCode::Char(c) if !ctrl => ctx.dispatch(AppCommand::HelpSearchInput(c)),
|
|
_ => {}
|
|
}
|
|
return InputResult::Continue;
|
|
}
|
|
|
|
match key.code {
|
|
KeyCode::Char('/') | KeyCode::Char('f') if key.code == KeyCode::Char('/') || ctrl => {
|
|
ctx.dispatch(AppCommand::HelpActivateSearch);
|
|
}
|
|
KeyCode::Esc if !ctx.app.ui.help_search_query.is_empty() => {
|
|
ctx.dispatch(AppCommand::HelpClearSearch);
|
|
}
|
|
KeyCode::Esc if ctx.app.ui.help_focused_block.is_some() => {
|
|
ctx.app.ui.help_focused_block = None;
|
|
ctx.app.ui.help_block_output = None;
|
|
}
|
|
KeyCode::Tab => ctx.dispatch(AppCommand::HelpToggleFocus),
|
|
KeyCode::Left if ctx.app.ui.help_focus == HelpFocus::Topics => {
|
|
collapse_help_section(ctx);
|
|
}
|
|
KeyCode::Right if ctx.app.ui.help_focus == HelpFocus::Topics => {
|
|
expand_help_section(ctx);
|
|
}
|
|
KeyCode::Char('n') if ctx.app.ui.help_focus == HelpFocus::Content => {
|
|
navigate_code_block(ctx, true);
|
|
}
|
|
KeyCode::Char('p') if ctx.app.ui.help_focus == HelpFocus::Content => {
|
|
navigate_code_block(ctx, false);
|
|
}
|
|
KeyCode::Enter
|
|
if ctx.app.ui.help_focus == HelpFocus::Content
|
|
&& ctx.app.ui.help_focused_block.is_some() =>
|
|
{
|
|
execute_focused_block(ctx);
|
|
}
|
|
KeyCode::Char('j') | KeyCode::Down if ctrl => {
|
|
ctx.dispatch(AppCommand::HelpNextTopic(5));
|
|
}
|
|
KeyCode::Char('k') | KeyCode::Up if ctrl => {
|
|
ctx.dispatch(AppCommand::HelpPrevTopic(5));
|
|
}
|
|
KeyCode::Char('j') | KeyCode::Down => match ctx.app.ui.help_focus {
|
|
HelpFocus::Topics => ctx.dispatch(AppCommand::HelpNextTopic(1)),
|
|
HelpFocus::Content => ctx.dispatch(AppCommand::HelpScrollDown(1)),
|
|
},
|
|
KeyCode::Char('k') | KeyCode::Up => match ctx.app.ui.help_focus {
|
|
HelpFocus::Topics => ctx.dispatch(AppCommand::HelpPrevTopic(1)),
|
|
HelpFocus::Content => ctx.dispatch(AppCommand::HelpScrollUp(1)),
|
|
},
|
|
KeyCode::PageDown => ctx.dispatch(AppCommand::HelpScrollDown(10)),
|
|
KeyCode::PageUp => ctx.dispatch(AppCommand::HelpScrollUp(10)),
|
|
KeyCode::Char('q') if !ctx.app.plugin_mode => {
|
|
ctx.dispatch(AppCommand::OpenModal(Modal::Confirm {
|
|
action: ConfirmAction::Quit,
|
|
selected: false,
|
|
}));
|
|
}
|
|
KeyCode::Char('s') => super::open_save(ctx),
|
|
KeyCode::Char('l') => super::open_load(ctx),
|
|
KeyCode::Char('?') => {
|
|
ctx.dispatch(AppCommand::OpenModal(Modal::KeybindingsHelp { scroll: 0 }));
|
|
}
|
|
KeyCode::Char(' ') if !ctx.app.plugin_mode => {
|
|
ctx.dispatch(AppCommand::TogglePlaying);
|
|
ctx.playing
|
|
.store(ctx.app.playback.playing, Ordering::Relaxed);
|
|
}
|
|
_ => {}
|
|
}
|
|
InputResult::Continue
|
|
}
|
|
|
|
fn navigate_code_block(ctx: &mut InputContext, forward: bool) {
|
|
let cache = ctx.app.ui.help_parsed.borrow();
|
|
let Some(parsed) = cache[ctx.app.ui.help_topic].as_ref() else {
|
|
return;
|
|
};
|
|
let count = parsed.code_blocks.len();
|
|
if count == 0 {
|
|
return;
|
|
}
|
|
let next = match ctx.app.ui.help_focused_block {
|
|
Some(cur) if forward => (cur + 1) % count,
|
|
Some(0) if !forward => count - 1,
|
|
Some(cur) if !forward => cur - 1,
|
|
_ if forward => 0,
|
|
_ => count - 1,
|
|
};
|
|
let scroll_to = parsed.code_blocks[next].start_line.saturating_sub(2);
|
|
drop(cache);
|
|
ctx.app.ui.help_focused_block = Some(next);
|
|
ctx.app.ui.help_block_output = None;
|
|
*ctx.app.ui.help_scroll_mut() = scroll_to;
|
|
}
|
|
|
|
fn execute_focused_block(ctx: &mut InputContext) {
|
|
let source = {
|
|
let cache = ctx.app.ui.help_parsed.borrow();
|
|
let Some(parsed) = cache[ctx.app.ui.help_topic].as_ref() else {
|
|
return;
|
|
};
|
|
let idx = ctx.app.ui.help_focused_block.unwrap();
|
|
let Some(block) = parsed.code_blocks.get(idx) else {
|
|
return;
|
|
};
|
|
block.source.clone()
|
|
};
|
|
let cleaned: String = source
|
|
.lines()
|
|
.map(|l| l.split(" => ").next().unwrap_or(l))
|
|
.collect::<Vec<_>>()
|
|
.join("\n");
|
|
let topic = ctx.app.ui.help_topic;
|
|
let block_idx = ctx.app.ui.help_focused_block.expect("block focused in code nav");
|
|
match ctx
|
|
.app
|
|
.execute_script_oneshot(&cleaned, ctx.link, ctx.audio_tx)
|
|
{
|
|
Ok(Some(output)) => {
|
|
ctx.app.ui.flash(&output, 200, FlashKind::Info);
|
|
ctx.app.ui.help_block_output = Some((topic, block_idx, output));
|
|
}
|
|
Ok(None) => ctx.app.ui.flash("Executed", 100, FlashKind::Info),
|
|
Err(e) => ctx
|
|
.app
|
|
.ui
|
|
.flash(&format!("Error: {e}"), 200, FlashKind::Error),
|
|
}
|
|
}
|
|
|
|
fn collapse_help_section(ctx: &mut InputContext) {
|
|
if let Some(s) = ctx.app.ui.help_on_section {
|
|
if ctx.app.ui.help_collapsed.get(s).copied().unwrap_or(false) {
|
|
return;
|
|
}
|
|
}
|
|
let section = match ctx.app.ui.help_on_section {
|
|
Some(s) => s,
|
|
None => docs::section_index_for_topic(ctx.app.ui.help_topic),
|
|
};
|
|
if let Some(v) = ctx.app.ui.help_collapsed.get_mut(section) {
|
|
*v = true;
|
|
}
|
|
ctx.app.ui.help_on_section = Some(section);
|
|
ctx.app.ui.help_focused_block = None;
|
|
ctx.app.ui.help_block_output = None;
|
|
}
|
|
|
|
fn expand_help_section(ctx: &mut InputContext) {
|
|
let Some(section) = ctx.app.ui.help_on_section else {
|
|
return;
|
|
};
|
|
if let Some(v) = ctx.app.ui.help_collapsed.get_mut(section) {
|
|
*v = false;
|
|
}
|
|
ctx.app.ui.help_on_section = None;
|
|
if let Some(first) = docs::first_topic_in_section(section) {
|
|
ctx.app.ui.help_topic = first;
|
|
}
|
|
ctx.app.ui.help_focused_block = None;
|
|
ctx.app.ui.help_block_output = None;
|
|
}
|
|
|
|
fn collapse_dict_section(ctx: &mut InputContext) {
|
|
if let Some(s) = ctx.app.ui.dict_on_section {
|
|
if ctx.app.ui.dict_collapsed.get(s).copied().unwrap_or(false) {
|
|
return;
|
|
}
|
|
}
|
|
let section = match ctx.app.ui.dict_on_section {
|
|
Some(s) => s,
|
|
None => categories::section_index_for_category(ctx.app.ui.dict_category),
|
|
};
|
|
if let Some(v) = ctx.app.ui.dict_collapsed.get_mut(section) {
|
|
*v = true;
|
|
}
|
|
ctx.app.ui.dict_on_section = Some(section);
|
|
}
|
|
|
|
fn expand_dict_section(ctx: &mut InputContext) {
|
|
let Some(section) = ctx.app.ui.dict_on_section else {
|
|
return;
|
|
};
|
|
if let Some(v) = ctx.app.ui.dict_collapsed.get_mut(section) {
|
|
*v = false;
|
|
}
|
|
ctx.app.ui.dict_on_section = None;
|
|
if let Some(first) = categories::first_category_in_section(section) {
|
|
ctx.app.ui.dict_category = first;
|
|
}
|
|
}
|
|
|
|
pub(super) fn handle_dict_page(ctx: &mut InputContext, key: KeyEvent) -> InputResult {
|
|
let ctrl = key.modifiers.contains(KeyModifiers::CONTROL);
|
|
|
|
if ctx.app.ui.dict_search_active {
|
|
match key.code {
|
|
KeyCode::Esc => ctx.dispatch(AppCommand::DictClearSearch),
|
|
KeyCode::Enter => ctx.dispatch(AppCommand::DictSearchConfirm),
|
|
KeyCode::Backspace => ctx.dispatch(AppCommand::DictSearchBackspace),
|
|
KeyCode::Char(c) if !ctrl => ctx.dispatch(AppCommand::DictSearchInput(c)),
|
|
_ => {}
|
|
}
|
|
return InputResult::Continue;
|
|
}
|
|
|
|
match key.code {
|
|
KeyCode::Char('/') | KeyCode::Char('f') if key.code == KeyCode::Char('/') || ctrl => {
|
|
ctx.dispatch(AppCommand::DictActivateSearch);
|
|
}
|
|
KeyCode::Esc if !ctx.app.ui.dict_search_query.is_empty() => {
|
|
ctx.dispatch(AppCommand::DictClearSearch);
|
|
}
|
|
KeyCode::Tab => ctx.dispatch(AppCommand::DictToggleFocus),
|
|
KeyCode::Left if ctx.app.ui.dict_focus == DictFocus::Categories => {
|
|
collapse_dict_section(ctx);
|
|
}
|
|
KeyCode::Right if ctx.app.ui.dict_focus == DictFocus::Categories => {
|
|
expand_dict_section(ctx);
|
|
}
|
|
KeyCode::Char('j') | KeyCode::Down => match ctx.app.ui.dict_focus {
|
|
DictFocus::Categories => ctx.dispatch(AppCommand::DictNextCategory),
|
|
DictFocus::Words => ctx.dispatch(AppCommand::DictScrollDown(1)),
|
|
},
|
|
KeyCode::Char('k') | KeyCode::Up => match ctx.app.ui.dict_focus {
|
|
DictFocus::Categories => ctx.dispatch(AppCommand::DictPrevCategory),
|
|
DictFocus::Words => ctx.dispatch(AppCommand::DictScrollUp(1)),
|
|
},
|
|
KeyCode::PageDown => ctx.dispatch(AppCommand::DictScrollDown(10)),
|
|
KeyCode::PageUp => ctx.dispatch(AppCommand::DictScrollUp(10)),
|
|
KeyCode::Char('q') if !ctx.app.plugin_mode => {
|
|
ctx.dispatch(AppCommand::OpenModal(Modal::Confirm {
|
|
action: ConfirmAction::Quit,
|
|
selected: false,
|
|
}));
|
|
}
|
|
KeyCode::Char('s') => super::open_save(ctx),
|
|
KeyCode::Char('l') => super::open_load(ctx),
|
|
KeyCode::Char('?') => {
|
|
ctx.dispatch(AppCommand::OpenModal(Modal::KeybindingsHelp { scroll: 0 }));
|
|
}
|
|
KeyCode::Char(' ') if !ctx.app.plugin_mode => {
|
|
ctx.dispatch(AppCommand::TogglePlaying);
|
|
ctx.playing
|
|
.store(ctx.app.playback.playing, Ordering::Relaxed);
|
|
}
|
|
_ => {}
|
|
}
|
|
InputResult::Continue
|
|
}
|