|  | 
|  | 1 | +use ratatui::{ | 
|  | 2 | +    layout::{Margin, Rect}, | 
|  | 3 | +    style::{Style, Stylize}, | 
|  | 4 | +    text::{Line, Span}, | 
|  | 5 | +    widgets::{Block, Borders, Paragraph}, | 
|  | 6 | +    Frame, | 
|  | 7 | +}; | 
|  | 8 | + | 
|  | 9 | +use crate::state::{AppState, Focus}; | 
|  | 10 | + | 
|  | 11 | +pub const SHORTCUT_LINES: usize = 2; | 
|  | 12 | + | 
|  | 13 | +pub struct ShortcutList { | 
|  | 14 | +    pub scope_name: &'static str, | 
|  | 15 | +    pub hints: Vec<Shortcut>, | 
|  | 16 | +} | 
|  | 17 | + | 
|  | 18 | +pub struct Shortcut { | 
|  | 19 | +    pub key_sequenses: Vec<Span<'static>>, | 
|  | 20 | +    pub desc: &'static str, | 
|  | 21 | +} | 
|  | 22 | + | 
|  | 23 | +pub fn span_vec_len(span_vec: &[Span]) -> usize { | 
|  | 24 | +    span_vec.iter().rfold(0, |init, s| init + s.width()) | 
|  | 25 | +} | 
|  | 26 | +impl ShortcutList { | 
|  | 27 | +    pub fn draw(&self, frame: &mut Frame, area: Rect) { | 
|  | 28 | +        let block = Block::default() | 
|  | 29 | +            .title(self.scope_name) | 
|  | 30 | +            .borders(Borders::all()); | 
|  | 31 | +        let inner_area = area.inner(Margin::new(1, 1)); | 
|  | 32 | +        let mut shortcut_list: Vec<Vec<Span>> = self.hints.iter().map(|h| h.to_spans()).collect(); | 
|  | 33 | + | 
|  | 34 | +        let mut lines = vec![Line::default(); SHORTCUT_LINES]; | 
|  | 35 | +        let mut idx = 0; | 
|  | 36 | + | 
|  | 37 | +        while idx < SHORTCUT_LINES - 1 { | 
|  | 38 | +            let split_idx = shortcut_list | 
|  | 39 | +                .iter() | 
|  | 40 | +                .scan(0usize, |total_len, s| { | 
|  | 41 | +                    *total_len += span_vec_len(s); | 
|  | 42 | +                    if *total_len > inner_area.width as usize { | 
|  | 43 | +                        None | 
|  | 44 | +                    } else { | 
|  | 45 | +                        *total_len += 4; | 
|  | 46 | +                        Some(1) | 
|  | 47 | +                    } | 
|  | 48 | +                }) | 
|  | 49 | +                .count(); | 
|  | 50 | +            let new_shortcut_list = shortcut_list.split_off(split_idx); | 
|  | 51 | +            let line: Vec<_> = shortcut_list | 
|  | 52 | +                .into_iter() | 
|  | 53 | +                .flat_map(|mut s| { | 
|  | 54 | +                    s.push(Span::default().content("    ")); | 
|  | 55 | +                    s | 
|  | 56 | +                }) | 
|  | 57 | +                .collect(); | 
|  | 58 | +            shortcut_list = new_shortcut_list; | 
|  | 59 | +            lines[idx] = line.into(); | 
|  | 60 | +            idx += 1; | 
|  | 61 | +        } | 
|  | 62 | +        lines[idx] = shortcut_list | 
|  | 63 | +            .into_iter() | 
|  | 64 | +            .flat_map(|mut s| { | 
|  | 65 | +                s.push(Span::default().content("    ")); | 
|  | 66 | +                s | 
|  | 67 | +            }) | 
|  | 68 | +            .collect(); | 
|  | 69 | + | 
|  | 70 | +        let p = Paragraph::new(lines).block(block); | 
|  | 71 | +        frame.render_widget(p, area); | 
|  | 72 | +    } | 
|  | 73 | +} | 
|  | 74 | + | 
|  | 75 | +impl Shortcut { | 
|  | 76 | +    pub fn new(key_sequences: Vec<&'static str>, desc: &'static str) -> Self { | 
|  | 77 | +        Self { | 
|  | 78 | +            key_sequenses: key_sequences | 
|  | 79 | +                .iter() | 
|  | 80 | +                .map(|s| Span::styled(*s, Style::default().bold())) | 
|  | 81 | +                .collect(), | 
|  | 82 | +            desc, | 
|  | 83 | +        } | 
|  | 84 | +    } | 
|  | 85 | + | 
|  | 86 | +    fn to_spans(&self) -> Vec<Span> { | 
|  | 87 | +        let mut ret: Vec<_> = self | 
|  | 88 | +            .key_sequenses | 
|  | 89 | +            .iter() | 
|  | 90 | +            .flat_map(|seq| { | 
|  | 91 | +                [ | 
|  | 92 | +                    Span::default().content("["), | 
|  | 93 | +                    seq.clone(), | 
|  | 94 | +                    Span::default().content("] "), | 
|  | 95 | +                ] | 
|  | 96 | +            }) | 
|  | 97 | +            .collect(); | 
|  | 98 | +        ret.push(Span::styled(self.desc, Style::default().italic())); | 
|  | 99 | +        ret | 
|  | 100 | +    } | 
|  | 101 | +} | 
|  | 102 | + | 
|  | 103 | +fn get_list_item_shortcut(state: &AppState) -> Shortcut { | 
|  | 104 | +    if state.selected_item_is_dir() { | 
|  | 105 | +        Shortcut::new(vec!["l", "Right", "Enter"], "Go to selected dir") | 
|  | 106 | +    } else { | 
|  | 107 | +        Shortcut::new(vec!["l", "Right", "Enter"], "Run selected command") | 
|  | 108 | +    } | 
|  | 109 | +} | 
|  | 110 | + | 
|  | 111 | +pub fn draw_shortcuts(state: &AppState, frame: &mut Frame, area: Rect) { | 
|  | 112 | +    match state.focus { | 
|  | 113 | +        Focus::Search => ShortcutList { | 
|  | 114 | +            scope_name: "Search bar", | 
|  | 115 | +            hints: vec![Shortcut::new(vec!["Enter"], "Finish search")], | 
|  | 116 | +        }, | 
|  | 117 | +        Focus::List => { | 
|  | 118 | +            let mut hints = Vec::new(); | 
|  | 119 | +            hints.push(Shortcut::new(vec!["q", "CTRL-c"], "Exit linutil")); | 
|  | 120 | +            if state.at_root() { | 
|  | 121 | +                hints.push(Shortcut::new(vec!["h", "Left", "Tab"], "Focus tab list")); | 
|  | 122 | +                hints.push(get_list_item_shortcut(state)); | 
|  | 123 | +            } else { | 
|  | 124 | +                if state.selected_item_is_up_dir() { | 
|  | 125 | +                    hints.push(Shortcut::new( | 
|  | 126 | +                        vec!["l", "Right", "Enter", "h", "Left"], | 
|  | 127 | +                        "Go to parrent directory", | 
|  | 128 | +                    )); | 
|  | 129 | +                } else { | 
|  | 130 | +                    hints.push(Shortcut::new(vec!["h", "Left"], "Go to parrent directory")); | 
|  | 131 | +                    hints.push(get_list_item_shortcut(state)); | 
|  | 132 | +                    if state.selected_item_is_cmd() { | 
|  | 133 | +                        hints.push(Shortcut::new(vec!["p"], "Enable preview")); | 
|  | 134 | +                    } | 
|  | 135 | +                } | 
|  | 136 | +                hints.push(Shortcut::new(vec!["Tab"], "Focus tab list")); | 
|  | 137 | +            }; | 
|  | 138 | +            hints.push(Shortcut::new(vec!["k", "Up"], "Select item above")); | 
|  | 139 | +            hints.push(Shortcut::new(vec!["j", "Down"], "Select item below")); | 
|  | 140 | +            hints.push(Shortcut::new(vec!["t"], "Next theme")); | 
|  | 141 | +            hints.push(Shortcut::new(vec!["T"], "Previous theme")); | 
|  | 142 | +            ShortcutList { | 
|  | 143 | +                scope_name: "Item list", | 
|  | 144 | +                hints, | 
|  | 145 | +            } | 
|  | 146 | +        } | 
|  | 147 | +        Focus::TabList => ShortcutList { | 
|  | 148 | +            scope_name: "Tab list", | 
|  | 149 | +            hints: vec![ | 
|  | 150 | +                Shortcut::new(vec!["q", "CTRL-c"], "Exit linutil"), | 
|  | 151 | +                Shortcut::new(vec!["l", "Right", "Tab", "Enter"], "Focus action list"), | 
|  | 152 | +                Shortcut::new(vec!["k", "Up"], "Select item above"), | 
|  | 153 | +                Shortcut::new(vec!["j", "Down"], "Select item below"), | 
|  | 154 | +                Shortcut::new(vec!["t"], "Next theme"), | 
|  | 155 | +                Shortcut::new(vec!["T"], "Previous theme"), | 
|  | 156 | +            ], | 
|  | 157 | +        }, | 
|  | 158 | +        Focus::FloatingWindow(ref float) => float.get_shortcut_list(), | 
|  | 159 | +    } | 
|  | 160 | +    .draw(frame, area); | 
|  | 161 | +} | 
0 commit comments