1use std::collections::{HashMap, HashSet};
2
3use ratatui::{
4 layout::{Constraint, Layout, Rect},
5 style::{Color, Style},
6 text::Line,
7 widgets::{Block, BorderType, List, ListDirection, Row, Table},
8 Frame,
9};
10
11use crate::{app::App, update::message::Message};
12
13pub(super) fn render_footer(app: &App, area: Rect, f: &mut Frame) {
14 let [left, right] = Layout::horizontal(Constraint::from_percentages([50, 50])).areas(area);
16
17 let console = draw_console(app);
19 let mut console_state = app.console_state().borrow_mut();
20 f.render_stateful_widget(console, left, &mut console_state);
21
22 let help_menu = build_help_table(app);
24 f.render_widget(help_menu, right);
25}
26
27fn draw_console(app: &App) -> List {
29 let items = app.console().to_owned();
30 List::new(items)
31 .block(Block::bordered().title("Console"))
32 .style(Style::default().fg(Color::White))
33 .highlight_symbol(">>")
34 .repeat_highlight_symbol(true)
35 .direction(ListDirection::TopToBottom)
36}
37
38fn build_help_table(app: &App) -> Table {
40 let key_style = Style::default().fg(Color::LightCyan);
41 let message_style = Style::default().fg(Color::Gray);
42
43 let keymaps = app.config().keymap();
44 let mut combined_keymaps = combine_keys_by_value(keymaps);
45 combined_keymaps.sort_unstable_by_key(|(_, message)| *message);
46
47 let longest_string = longest_combined_keymap(&combined_keymaps);
48 let widths = [Constraint::Length(longest_string), Constraint::Min(10)];
49
50 let keymap_rows = combined_keymaps.iter().map(|(keybinds, message)| {
51 Row::new([
52 Line::styled(keybinds.to_owned(), key_style),
53 Line::styled(message.to_string(), message_style),
54 ])
55 });
56
57 Table::new(keymap_rows, widths).block(
58 Block::bordered()
59 .border_type(BorderType::Plain)
60 .title("Help"),
61 )
62}
63
64fn longest_combined_keymap(combined_keymaps: &Vec<(String, &Message)>) -> u16 {
65 combined_keymaps
66 .iter()
67 .map(|(keys, _)| keys.len().try_into().unwrap())
68 .max()
69 .unwrap()
70}
71
72fn combine_keys_by_value(map: &HashMap<String, Message>) -> Vec<(String, &Message)> {
73 let mut result = Vec::new();
74 let mut processed_messages = HashSet::new();
75
76 for (_, message) in map.iter() {
77 if !processed_messages.contains(message) {
78 let combined_keys = map
79 .into_iter()
80 .filter(|(_, m)| *m == message)
81 .enumerate()
82 .map(|(i, (k, _))| {
83 if i == 0 {
84 k.to_owned().to_uppercase()
85 } else {
86 format!(" | {}", k.to_uppercase())
87 }
88 })
89 .collect::<String>();
90 result.push((combined_keys, message));
91 processed_messages.insert(message);
92 }
93 }
94
95 result
96}