use anyhow::Result; use ratatui_widgets::block::Padding; use yazi_binding::position::Position; use yazi_config::{KEYMAP, keymap::ChordArc}; use yazi_macro::render; use yazi_shared::Layer; use yazi_term::event::KeyEvent; use yazi_tty::sequence::SetCursorStyle; use yazi_widgets::{Scrollable, input::Input}; #[derive(Default)] pub struct Help { pub visible: bool, pub layer: Layer, pub position: Position, pub bindings: Vec, // Filter pub input: Input, pub keyword: String, pub offset: usize, pub cursor: usize, pub height: u16, } impl Help { pub fn r#type(&mut self, key: KeyEvent) -> Result { if !self.input.r#type(key)? { return Ok(false); } self.filter_apply(); Ok(true) } pub fn filter_apply(&mut self) { let kw = self.input.value(); if kw.is_empty() { self.keyword.clear(); self.bindings = KEYMAP.chords(self.layer).iter().cloned().collect(); } else if self.keyword != kw { self.keyword = kw.to_owned(); self.bindings = Self::filter_chords(&KEYMAP.chords(self.layer), kw); } render!(self.scroll(0)); } fn filter_chords(chords: &[ChordArc], kw: &str) -> Vec { let lowercased = kw.to_lowercase(); let mut exact = vec![]; let mut icase = vec![]; let mut contains = vec![]; let mut desc = vec![]; for c in chords { let on = c.on(); if on.starts_with(kw) { exact.push(c.clone()); } else if on.to_lowercase().starts_with(&lowercased) { icase.push(c.clone()); } else if on.to_lowercase().contains(&lowercased) { contains.push(c.clone()); } else if c.desc_or_run().to_lowercase().contains(&lowercased) { desc.push(c.clone()); } } exact.extend(icase); exact.extend(contains); exact.extend(desc); exact } } impl Help { pub fn padding(&self) -> Padding { Padding::new(1, 1, 1, 1) } // --- Bindings pub fn window(&self) -> &[ChordArc] { let end = (self.offset + self.limit()).min(self.bindings.len()); &self.bindings[self.offset..end] } // --- Cursor pub fn cursor(&self) -> Option { self.visible.then_some(self.input.cursor()) } pub fn rel_cursor(&self) -> usize { self.cursor - self.offset } pub fn cursor_shape(&self) -> Option { self.visible.then_some(self.input.cursor_shape()) } } impl Scrollable for Help { fn total(&self) -> usize { self.bindings.len() } fn limit(&self) -> usize { let p = self.padding(); self.height.saturating_sub(p.top + /* input */ 1 + /* divider */ 1 + p.bottom) as usize } fn cursor_mut(&mut self) -> &mut usize { &mut self.cursor } fn offset_mut(&mut self) -> &mut usize { &mut self.offset } }