1 use super::{
2     visibility_blocking, CommandBlocking, CommandInfo, Component,
3     DrawableComponent,
4 };
5 use crate::{keys::SharedKeyConfig, strings, ui, version::Version};
6 use asyncgit::hash;
7 use crossterm::event::Event;
8 use itertools::Itertools;
9 use std::{borrow::Cow, cmp, convert::TryFrom};
10 use tui::{
11     backend::Backend,
12     layout::{Alignment, Constraint, Direction, Layout, Rect},
13     style::{Modifier, Style},
14     widgets::{Block, BorderType, Borders, Clear, Paragraph, Text},
15     Frame,
16 };
17 
18 use anyhow::Result;
19 use ui::style::SharedTheme;
20 
21 ///
22 pub struct HelpComponent {
23     cmds: Vec<CommandInfo>,
24     visible: bool,
25     selection: u16,
26     theme: SharedTheme,
27     key_config: SharedKeyConfig,
28 }
29 
30 impl DrawableComponent for HelpComponent {
draw<B: Backend>( &self, f: &mut Frame<B>, _rect: Rect, ) -> Result<()>31     fn draw<B: Backend>(
32         &self,
33         f: &mut Frame<B>,
34         _rect: Rect,
35     ) -> Result<()> {
36         if self.visible {
37             const SIZE: (u16, u16) = (65, 24);
38             let scroll_threshold = SIZE.1 / 3;
39             let scroll =
40                 self.selection.saturating_sub(scroll_threshold);
41 
42             let area =
43                 ui::centered_rect_absolute(SIZE.0, SIZE.1, f.size());
44 
45             f.render_widget(Clear, area);
46             f.render_widget(
47                 Block::default()
48                     .title(&strings::help_title(&self.key_config))
49                     .borders(Borders::ALL)
50                     .border_type(BorderType::Thick),
51                 area,
52             );
53 
54             let chunks = Layout::default()
55                 .vertical_margin(1)
56                 .horizontal_margin(1)
57                 .direction(Direction::Vertical)
58                 .constraints(
59                     [Constraint::Min(1), Constraint::Length(1)]
60                         .as_ref(),
61                 )
62                 .split(area);
63 
64             f.render_widget(
65                 Paragraph::new(self.get_text().iter())
66                     .scroll(scroll)
67                     .alignment(Alignment::Left),
68                 chunks[0],
69             );
70 
71             f.render_widget(
72                 Paragraph::new(
73                     vec![Text::Styled(
74                         Cow::from(format!(
75                             "gitui {}",
76                             Version::new(),
77                         )),
78                         Style::default(),
79                     )]
80                     .iter(),
81                 )
82                 .alignment(Alignment::Right),
83                 chunks[1],
84             );
85         }
86 
87         Ok(())
88     }
89 }
90 
91 impl Component for HelpComponent {
commands( &self, out: &mut Vec<CommandInfo>, force_all: bool, ) -> CommandBlocking92     fn commands(
93         &self,
94         out: &mut Vec<CommandInfo>,
95         force_all: bool,
96     ) -> CommandBlocking {
97         // only if help is open we have no other commands available
98         if self.visible && !force_all {
99             out.clear();
100         }
101 
102         if self.visible {
103             out.push(CommandInfo::new(
104                 strings::commands::scroll(&self.key_config),
105                 true,
106                 true,
107             ));
108 
109             out.push(CommandInfo::new(
110                 strings::commands::close_popup(&self.key_config),
111                 true,
112                 true,
113             ));
114         }
115 
116         if !self.visible || force_all {
117             out.push(
118                 CommandInfo::new(
119                     strings::commands::help_open(&self.key_config),
120                     true,
121                     true,
122                 )
123                 .order(99),
124             );
125         }
126 
127         visibility_blocking(self)
128     }
129 
event(&mut self, ev: Event) -> Result<bool>130     fn event(&mut self, ev: Event) -> Result<bool> {
131         if self.visible {
132             if let Event::Key(e) = ev {
133                 if e == self.key_config.exit_popup {
134                     self.hide()
135                 } else if e == self.key_config.move_down {
136                     self.move_selection(true)
137                 } else if e == self.key_config.move_up {
138                     self.move_selection(false)
139                 } else {
140                 }
141             }
142 
143             Ok(true)
144         } else if let Event::Key(k) = ev {
145             if k == self.key_config.open_help {
146                 self.show()?;
147                 Ok(true)
148             } else {
149                 Ok(false)
150             }
151         } else {
152             Ok(false)
153         }
154     }
155 
is_visible(&self) -> bool156     fn is_visible(&self) -> bool {
157         self.visible
158     }
159 
hide(&mut self)160     fn hide(&mut self) {
161         self.visible = false
162     }
163 
show(&mut self) -> Result<()>164     fn show(&mut self) -> Result<()> {
165         self.visible = true;
166 
167         Ok(())
168     }
169 }
170 
171 impl HelpComponent {
new( theme: SharedTheme, key_config: SharedKeyConfig, ) -> Self172     pub const fn new(
173         theme: SharedTheme,
174         key_config: SharedKeyConfig,
175     ) -> Self {
176         Self {
177             cmds: vec![],
178             visible: false,
179             selection: 0,
180             theme,
181             key_config,
182         }
183     }
184     ///
set_cmds(&mut self, cmds: Vec<CommandInfo>)185     pub fn set_cmds(&mut self, cmds: Vec<CommandInfo>) {
186         self.cmds = cmds
187             .into_iter()
188             .filter(|e| !e.text.hide_help)
189             .collect::<Vec<_>>();
190         self.cmds.sort_by_key(|e| e.text.clone());
191         self.cmds.dedup_by_key(|e| e.text.clone());
192         self.cmds.sort_by_key(|e| hash(&e.text.group));
193     }
194 
move_selection(&mut self, inc: bool)195     fn move_selection(&mut self, inc: bool) {
196         let mut new_selection = self.selection;
197 
198         new_selection = if inc {
199             new_selection.saturating_add(1)
200         } else {
201             new_selection.saturating_sub(1)
202         };
203         new_selection = cmp::max(new_selection, 0);
204 
205         if let Ok(max) =
206             u16::try_from(self.cmds.len().saturating_sub(1))
207         {
208             self.selection = cmp::min(new_selection, max);
209         }
210     }
211 
get_text(&self) -> Vec<Text>212     fn get_text(&self) -> Vec<Text> {
213         let mut txt = Vec::new();
214 
215         let mut processed = 0_u16;
216 
217         for (key, group) in
218             &self.cmds.iter().group_by(|e| e.text.group)
219         {
220             txt.push(Text::Styled(
221                 Cow::from(format!("{}\n", key)),
222                 Style::default().modifier(Modifier::REVERSED),
223             ));
224 
225             txt.extend(
226                 group
227                     .sorted_by_key(|e| e.order)
228                     .map(|e| {
229                         let is_selected = self.selection == processed;
230 
231                         processed += 1;
232 
233                         let mut out = String::from(if is_selected {
234                             ">"
235                         } else {
236                             " "
237                         });
238 
239                         e.print(&mut out);
240                         out.push('\n');
241 
242                         if is_selected {
243                             out.push_str(
244                                 format!("  {}\n", e.text.desc)
245                                     .as_str(),
246                             );
247                         }
248 
249                         Text::Styled(
250                             Cow::from(out),
251                             self.theme.text(true, is_selected),
252                         )
253                     })
254                     .collect::<Vec<_>>(),
255             );
256         }
257 
258         txt
259     }
260 }
261