1 use {
2     super::help_content,
3     crate::{
4         app::*,
5         command::{Command, TriggerType},
6         conf::Conf,
7         display::{Screen, W},
8         errors::ProgramError,
9         launchable::Launchable,
10         pattern::*,
11         tree::TreeOptions,
12         verb::*,
13     },
14     std::path::{Path, PathBuf},
15     termimad::{Area, FmtText, TextView},
16 };
17 
18 /// an application state dedicated to help
19 pub struct HelpState {
20     pub scroll: usize,
21     pub text_area: Area,
22     dirty: bool, // background must be cleared
23     pattern: Pattern,
24     tree_options: TreeOptions,
25     config_path: PathBuf, // the last config path when several were used
26     mode: Mode,
27 }
28 
29 impl HelpState {
new( tree_options: TreeOptions, _screen: Screen, con: &AppContext, ) -> HelpState30     pub fn new(
31         tree_options: TreeOptions,
32         _screen: Screen,
33         con: &AppContext,
34     ) -> HelpState {
35         let text_area = Area::uninitialized(); // will be fixed at drawing time
36         let config_path = con.config_paths
37             .last()
38             .cloned()
39             .unwrap_or_else(Conf::default_location);
40         HelpState {
41             text_area,
42             scroll: 0,
43             dirty: true,
44             pattern: Pattern::None,
45             tree_options,
46             config_path,
47             mode: initial_mode(con),
48         }
49     }
50 }
51 
52 impl PanelState for HelpState {
53 
get_type(&self) -> PanelStateType54     fn get_type(&self) -> PanelStateType {
55         PanelStateType::Help
56     }
57 
set_mode(&mut self, mode: Mode)58     fn set_mode(&mut self, mode: Mode) {
59         self.mode = mode;
60     }
61 
get_mode(&self) -> Mode62     fn get_mode(&self) -> Mode {
63         self.mode
64     }
65 
selected_path(&self) -> Option<&Path>66     fn selected_path(&self) -> Option<&Path> {
67         Some(&self.config_path)
68     }
69 
tree_options(&self) -> TreeOptions70     fn tree_options(&self) -> TreeOptions {
71         self.tree_options.clone()
72     }
73 
with_new_options( &mut self, _screen: Screen, change_options: &dyn Fn(&mut TreeOptions), _in_new_panel: bool, _con: &AppContext, ) -> CmdResult74     fn with_new_options(
75         &mut self,
76         _screen: Screen,
77         change_options: &dyn Fn(&mut TreeOptions),
78         _in_new_panel: bool, // TODO open a tree if true
79         _con: &AppContext,
80     ) -> CmdResult {
81         change_options(&mut self.tree_options);
82         CmdResult::Keep
83     }
84 
selection(&self) -> Option<Selection<'_>>85     fn selection(&self) -> Option<Selection<'_>> {
86         Some(Selection {
87             path: &self.config_path,
88             stype: SelectionType::File,
89             is_exe: false,
90             line: 0,
91         })
92     }
93 
refresh(&mut self, _screen: Screen, _con: &AppContext) -> Command94     fn refresh(&mut self, _screen: Screen, _con: &AppContext) -> Command {
95         self.dirty = true;
96         Command::empty()
97     }
98 
on_pattern( &mut self, pat: InputPattern, _app_state: &AppState, _con: &AppContext, ) -> Result<CmdResult, ProgramError>99     fn on_pattern(
100         &mut self,
101         pat: InputPattern,
102         _app_state: &AppState,
103         _con: &AppContext,
104     ) -> Result<CmdResult, ProgramError> {
105         self.pattern = pat.pattern;
106         Ok(CmdResult::Keep)
107     }
108 
display( &mut self, w: &mut W, disc: &DisplayContext, ) -> Result<(), ProgramError>109     fn display(
110         &mut self,
111         w: &mut W,
112         disc: &DisplayContext,
113     ) -> Result<(), ProgramError> {
114         let con = &disc.con;
115         let mut text_area = disc.state_area.clone();
116         text_area.pad_for_max_width(120);
117         if text_area != self.text_area {
118             self.dirty = true;
119             self.text_area = text_area;
120         }
121         if self.dirty {
122             disc.panel_skin.styles.default.queue_bg(w)?;
123             disc.screen.clear_area_to_right(w, &disc.state_area)?;
124             self.dirty = false;
125         }
126         let mut expander = help_content::expander();
127         expander.set("version", env!("CARGO_PKG_VERSION"));
128         let config_paths: Vec<String> = con.config_paths.iter()
129             .map(|p| p.to_string_lossy().to_string())
130             .collect();
131         for path in &config_paths {
132             expander.sub("config-files")
133                 .set("path", path);
134         }
135         let verb_rows = super::help_verbs::matching_verb_rows(&self.pattern, con);
136         for row in &verb_rows {
137             let sub = expander
138                 .sub("verb-rows")
139                 .set_md("name", row.name())
140                 .set_md("shortcut", row.shortcut())
141                 .set("key", &row.verb.keys_desc);
142             if row.verb.description.code {
143                 sub.set("description", "");
144                 sub.set("execution", &row.verb.description.content);
145             } else {
146                 sub.set_md("description", &row.verb.description.content);
147                 sub.set("execution", "");
148             }
149         }
150         let search_rows = super::help_search_modes::search_mode_rows(con);
151         for row in &search_rows {
152             expander
153                 .sub("search-mode-rows")
154                 .set("search-prefix", &row.prefix)
155                 .set("search-type", &row.description);
156         }
157         let features = super::help_features::list();
158         expander.set(
159             "features-text",
160             if features.is_empty() {
161                 "This release was compiled with no optional feature enabled."
162             } else {
163                 "This release was compiled with those optional features enabled:"
164             },
165         );
166         for feature in &features {
167             expander
168                 .sub("features")
169                 .set("feature-name", feature.0)
170                 .set("feature-description", feature.1);
171         }
172         let text = expander.expand();
173         let fmt_text = FmtText::from_text(
174             &disc.panel_skin.help_skin,
175             text,
176             Some((self.text_area.width - 1) as usize),
177         );
178         let mut text_view = TextView::from(&self.text_area, &fmt_text);
179         self.scroll = text_view.set_scroll(self.scroll);
180         Ok(text_view.write_on(w)?)
181     }
182 
on_internal( &mut self, w: &mut W, internal_exec: &InternalExecution, input_invocation: Option<&VerbInvocation>, trigger_type: TriggerType, app_state: &mut AppState, cc: &CmdContext, ) -> Result<CmdResult, ProgramError>183     fn on_internal(
184         &mut self,
185         w: &mut W,
186         internal_exec: &InternalExecution,
187         input_invocation: Option<&VerbInvocation>,
188         trigger_type: TriggerType,
189         app_state: &mut AppState,
190         cc: &CmdContext,
191     ) -> Result<CmdResult, ProgramError> {
192         use Internal::*;
193         Ok(match internal_exec.internal {
194             Internal::back => {
195                 if self.pattern.is_some() {
196                     self.pattern = Pattern::None;
197                     CmdResult::Keep
198                 } else {
199                     CmdResult::PopState
200                 }
201             }
202             help => CmdResult::Keep,
203             line_down | line_down_no_cycle => {
204                 self.scroll += get_arg(input_invocation, internal_exec, 1);
205                 CmdResult::Keep
206             }
207             line_up | line_up_no_cycle => {
208                 let dy = get_arg(input_invocation, internal_exec, 1);
209                 self.scroll = if self.scroll > dy {
210                     self.scroll - dy
211                 } else {
212                     0
213                 };
214                 CmdResult::Keep
215             }
216             open_stay => match open::that(&Conf::default_location()) {
217                 Ok(exit_status) => {
218                     info!("open returned with exit_status {:?}", exit_status);
219                     CmdResult::Keep
220                 }
221                 Err(e) => CmdResult::DisplayError(format!("{:?}", e)),
222             },
223             // FIXME check we can't use the generic one
224             open_leave => {
225                 CmdResult::from(Launchable::opener(
226                     Conf::default_location()
227                 ))
228             }
229             page_down => {
230                 self.scroll += self.text_area.height as usize;
231                 CmdResult::Keep
232             }
233             page_up => {
234                 let height = self.text_area.height as usize;
235                 self.scroll = if self.scroll > height {
236                     self.scroll - self.text_area.height as usize
237                 } else {
238                     0
239                 };
240                 CmdResult::Keep
241             }
242             _ => self.on_internal_generic(
243                 w,
244                 internal_exec,
245                 input_invocation,
246                 trigger_type,
247                 app_state,
248                 cc,
249             )?,
250         })
251     }
252 }
253 
254