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