1 use { 2 super::*, 3 crate::{ 4 app::*, 5 command::{Command, ScrollCommand, TriggerType}, 6 display::{Screen, W}, 7 errors::ProgramError, 8 flag::Flag, 9 pattern::InputPattern, 10 stage::*, 11 task_sync::Dam, 12 tree::TreeOptions, 13 verb::*, 14 }, 15 crossterm::{ 16 cursor, 17 QueueableCommand, 18 }, 19 std::path::{Path, PathBuf}, 20 termimad::{Area, CropWriter, SPACE_FILLING}, 21 }; 22 23 /// an application state dedicated to previewing files. 24 /// It's usually the only state in its panel and is kept when 25 /// the selection changes (other panels indirectly call 26 /// set_selected_path). 27 pub struct PreviewState { 28 pub preview_area: Area, 29 dirty: bool, // true when background must be cleared 30 path: PathBuf, // path to the previewed file 31 preview: Preview, 32 pending_pattern: InputPattern, // a pattern (or not) which has not yet be applied 33 filtered_preview: Option<Preview>, 34 removed_pattern: InputPattern, 35 prefered_mode: Option<PreviewMode>, 36 tree_options: TreeOptions, 37 mode: Mode, 38 } 39 40 impl PreviewState { new( path: PathBuf, pending_pattern: InputPattern, prefered_mode: Option<PreviewMode>, tree_options: TreeOptions, con: &AppContext, ) -> PreviewState41 pub fn new( 42 path: PathBuf, 43 pending_pattern: InputPattern, 44 prefered_mode: Option<PreviewMode>, 45 tree_options: TreeOptions, 46 con: &AppContext, 47 ) -> PreviewState { 48 let preview_area = Area::uninitialized(); // will be fixed at drawing time 49 let preview = Preview::new(&path, prefered_mode, con); 50 PreviewState { 51 preview_area, 52 dirty: true, 53 path, 54 preview, 55 pending_pattern, 56 filtered_preview: None, 57 removed_pattern: InputPattern::none(), 58 prefered_mode, 59 tree_options, 60 mode: initial_mode(con), 61 } 62 } vis_preview(&self) -> &Preview63 fn vis_preview(&self) -> &Preview { 64 self.filtered_preview.as_ref().unwrap_or(&self.preview) 65 } mut_preview(&mut self) -> &mut Preview66 fn mut_preview(&mut self) -> &mut Preview { 67 self.filtered_preview.as_mut().unwrap_or(&mut self.preview) 68 } set_mode( &mut self, mode: PreviewMode, con: &AppContext, ) -> Result<CmdResult, ProgramError>69 fn set_mode( 70 &mut self, 71 mode: PreviewMode, 72 con: &AppContext, 73 ) -> Result<CmdResult, ProgramError> { 74 if self.preview.get_mode() == Some(mode) { 75 return Ok(CmdResult::Keep); 76 } 77 Ok(match Preview::with_mode(&self.path, mode, con) { 78 Ok(preview) => { 79 self.preview = preview; 80 self.prefered_mode = Some(mode); 81 CmdResult::Keep 82 } 83 Err(e) => { 84 CmdResult::DisplayError( 85 format!("Can't display as {:?} : {:?}", mode, e) 86 ) 87 } 88 }) 89 } 90 no_opt_selection(&self) -> Selection<'_>91 fn no_opt_selection(&self) -> Selection<'_> { 92 Selection { 93 path: &self.path, 94 stype: SelectionType::File, 95 is_exe: false, // not always true. It means :open_leave won't execute it 96 line: self.vis_preview().get_selected_line_number().unwrap_or(0), 97 } 98 } 99 100 } 101 102 impl PanelState for PreviewState { 103 get_type(&self) -> PanelStateType104 fn get_type(&self) -> PanelStateType { 105 PanelStateType::Preview 106 } 107 set_mode(&mut self, mode: Mode)108 fn set_mode(&mut self, mode: Mode) { 109 self.mode = mode; 110 } 111 get_mode(&self) -> Mode112 fn get_mode(&self) -> Mode { 113 self.mode 114 } 115 get_pending_task(&self) -> Option<&'static str>116 fn get_pending_task(&self) -> Option<&'static str> { 117 if self.pending_pattern.is_some() { 118 Some("searching") 119 } else { 120 None 121 } 122 } 123 on_pattern( &mut self, pat: InputPattern, _app_state: &AppState, _con: &AppContext, ) -> Result<CmdResult, ProgramError>124 fn on_pattern( 125 &mut self, 126 pat: InputPattern, 127 _app_state: &AppState, 128 _con: &AppContext, 129 ) -> Result<CmdResult, ProgramError> { 130 if pat.is_none() { 131 if let Some(filtered_preview) = self.filtered_preview.take() { 132 let old_selection = filtered_preview.get_selected_line_number(); 133 if let Some(number) = old_selection { 134 self.preview.try_select_line_number(number); 135 } 136 self.removed_pattern = filtered_preview.pattern(); 137 } 138 } else { 139 if !self.preview.is_filterable() { 140 return Ok(CmdResult::error("this preview can't be searched")); 141 } 142 } 143 self.pending_pattern = pat; 144 Ok(CmdResult::Keep) 145 } 146 147 /// do the preview filtering if required and not yet done do_pending_task( &mut self, _stage: &Stage, _screen: Screen, con: &AppContext, dam: &mut Dam, )148 fn do_pending_task( 149 &mut self, 150 _stage: &Stage, 151 _screen: Screen, 152 con: &AppContext, 153 dam: &mut Dam, 154 ) { 155 if self.pending_pattern.is_some() { 156 let old_selection = self 157 .filtered_preview 158 .as_ref() 159 .and_then(|p| p.get_selected_line_number()) 160 .or_else(|| self.preview.get_selected_line_number()); 161 let pattern = self.pending_pattern.take(); 162 self.filtered_preview = time!( 163 Info, 164 "preview filtering", 165 self.preview.filtered(&self.path, pattern, dam, con), 166 ); // can be None if a cancellation was required 167 if let Some(ref mut filtered_preview) = self.filtered_preview { 168 if let Some(number) = old_selection { 169 filtered_preview.try_select_line_number(number); 170 } 171 } 172 } 173 } 174 selected_path(&self) -> Option<&Path>175 fn selected_path(&self) -> Option<&Path> { 176 Some(&self.path) 177 } 178 set_selected_path(&mut self, path: PathBuf, con: &AppContext)179 fn set_selected_path(&mut self, path: PathBuf, con: &AppContext) { 180 if let Some(fp) = &self.filtered_preview { 181 self.pending_pattern = fp.pattern(); 182 }; 183 self.preview = Preview::new(&path, self.prefered_mode, con); 184 self.path = path; 185 } 186 selection(&self) -> Option<Selection<'_>>187 fn selection(&self) -> Option<Selection<'_>> { 188 Some(self.no_opt_selection()) 189 } 190 tree_options(&self) -> TreeOptions191 fn tree_options(&self) -> TreeOptions { 192 self.tree_options.clone() 193 } 194 with_new_options( &mut self, _screen: Screen, change_options: &dyn Fn(&mut TreeOptions), _in_new_panel: bool, _con: &AppContext, ) -> CmdResult195 fn with_new_options( 196 &mut self, 197 _screen: Screen, 198 change_options: &dyn Fn(&mut TreeOptions), 199 _in_new_panel: bool, // TODO open tree if true 200 _con: &AppContext, 201 ) -> CmdResult { 202 change_options(&mut self.tree_options); 203 CmdResult::Keep 204 } 205 refresh(&mut self, _screen: Screen, con: &AppContext) -> Command206 fn refresh(&mut self, _screen: Screen, con: &AppContext) -> Command { 207 self.dirty = true; 208 self.set_selected_path(self.path.clone(), con); 209 Command::empty() 210 } 211 on_click( &mut self, _x: u16, y: u16, _screen: Screen, _con: &AppContext, ) -> Result<CmdResult, ProgramError>212 fn on_click( 213 &mut self, 214 _x: u16, 215 y: u16, 216 _screen: Screen, 217 _con: &AppContext, 218 ) -> Result<CmdResult, ProgramError> { 219 if y >= self.preview_area.top && y < self.preview_area.top + self.preview_area.height { 220 let y = y - self.preview_area.top; 221 self.mut_preview().try_select_y(y); 222 } 223 Ok(CmdResult::Keep) 224 } 225 display( &mut self, w: &mut W, disc: &DisplayContext, ) -> Result<(), ProgramError>226 fn display( 227 &mut self, 228 w: &mut W, 229 disc: &DisplayContext, 230 ) -> Result<(), ProgramError> { 231 let con = &disc.con; 232 let state_area = &disc.state_area; 233 if state_area.height < 3 { 234 warn!("area too small for preview"); 235 return Ok(()); 236 } 237 let mut preview_area = state_area.clone(); 238 preview_area.height -= 1; 239 preview_area.top += 1; 240 if preview_area != self.preview_area { 241 self.dirty = true; 242 self.preview_area = preview_area; 243 } 244 if self.dirty { 245 disc.panel_skin.styles.default.queue_bg(w)?; 246 disc.screen.clear_area_to_right(w, state_area)?; 247 self.dirty = false; 248 } 249 let styles = &disc.panel_skin.styles; 250 w.queue(cursor::MoveTo(state_area.left, 0))?; 251 let mut cw = CropWriter::new(w, state_area.width as usize); 252 let file_name = self 253 .path 254 .file_name() 255 .map(|n| n.to_string_lossy().to_string()) 256 .unwrap_or_else(|| "???".to_string()); 257 cw.queue_str(&styles.preview_title, &file_name)?; 258 let info_area = Area::new( 259 state_area.left + state_area.width - cw.allowed as u16, 260 state_area.top, 261 cw.allowed as u16, 262 1, 263 ); 264 cw.fill(&styles.preview_title, &SPACE_FILLING)?; 265 let preview = self.filtered_preview.as_mut().unwrap_or(&mut self.preview); 266 preview.display_info(w, disc.screen, disc.panel_skin, &info_area)?; 267 if let Err(err) = preview.display(w, disc.screen, disc.panel_skin, &self.preview_area, con) { 268 warn!("error while displaying file: {:?}", &err); 269 if preview.get_mode().is_some() { 270 // means it's not an error already 271 if let ProgramError::Io { source } = err { 272 // we mutate the preview to Preview::IOError 273 self.preview = Preview::IoError(source); 274 return self.display(w, disc); 275 } 276 } 277 return Err(err); 278 } 279 Ok(()) 280 } 281 no_verb_status( &self, has_previous_state: bool, con: &AppContext, ) -> Status282 fn no_verb_status( 283 &self, 284 has_previous_state: bool, 285 con: &AppContext, 286 ) -> Status { 287 let mut ssb = con.standard_status.builder( 288 PanelStateType::Preview, 289 self.no_opt_selection(), 290 ); 291 ssb.has_previous_state = has_previous_state; 292 ssb.is_filtered = self.filtered_preview.is_some(); 293 ssb.has_removed_pattern = self.removed_pattern.is_some(); 294 ssb.status() 295 } 296 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>297 fn on_internal( 298 &mut self, 299 w: &mut W, 300 internal_exec: &InternalExecution, 301 input_invocation: Option<&VerbInvocation>, 302 trigger_type: TriggerType, 303 app_state: &mut AppState, 304 cc: &CmdContext, 305 ) -> Result<CmdResult, ProgramError> { 306 let con = &cc.app.con; 307 match internal_exec.internal { 308 Internal::back => { 309 if self.filtered_preview.is_some() { 310 self.on_pattern(InputPattern::none(), app_state, con) 311 } else { 312 Ok(CmdResult::PopState) 313 } 314 } 315 Internal::copy_line => { 316 #[cfg(not(feature = "clipboard"))] 317 { 318 Ok(CmdResult::error("Clipboard feature not enabled at compilation")) 319 } 320 #[cfg(feature = "clipboard")] 321 { 322 Ok(match self.mut_preview().get_selected_line() { 323 Some(line) => { 324 match terminal_clipboard::set_string(line) { 325 Ok(()) => CmdResult::Keep, 326 Err(_) => CmdResult::error("Clipboard error while copying path"), 327 } 328 } 329 None => CmdResult::error("No selected line in preview"), 330 }) 331 } 332 } 333 Internal::line_down => { 334 let count = get_arg(input_invocation, internal_exec, 1); 335 self.mut_preview().move_selection(count, true); 336 Ok(CmdResult::Keep) 337 } 338 Internal::line_up => { 339 let count = get_arg(input_invocation, internal_exec, 1); 340 self.mut_preview().move_selection(-count, true); 341 Ok(CmdResult::Keep) 342 } 343 Internal::line_down_no_cycle => { 344 let count = get_arg(input_invocation, internal_exec, 1); 345 self.mut_preview().move_selection(count, false); 346 Ok(CmdResult::Keep) 347 } 348 Internal::line_up_no_cycle => { 349 let count = get_arg(input_invocation, internal_exec, 1); 350 self.mut_preview().move_selection(-count, false); 351 Ok(CmdResult::Keep) 352 } 353 Internal::page_down => { 354 self.mut_preview().try_scroll(ScrollCommand::Pages(1)); 355 Ok(CmdResult::Keep) 356 } 357 Internal::page_up => { 358 self.mut_preview().try_scroll(ScrollCommand::Pages(-1)); 359 Ok(CmdResult::Keep) 360 } 361 //Internal::restore_pattern => { 362 // debug!("restore_pattern"); 363 // self.pending_pattern = self.removed_pattern.take(); 364 // Ok(CmdResult::Keep) 365 //} 366 Internal::panel_left if self.removed_pattern.is_some() => { 367 self.pending_pattern = self.removed_pattern.take(); 368 Ok(CmdResult::Keep) 369 } 370 Internal::panel_right if self.filtered_preview.is_some() => { 371 self.on_pattern(InputPattern::none(), app_state, con) 372 } 373 Internal::select_first => { 374 self.mut_preview().select_first(); 375 Ok(CmdResult::Keep) 376 } 377 Internal::select_last => { 378 self.mut_preview().select_last(); 379 Ok(CmdResult::Keep) 380 } 381 Internal::preview_image => self.set_mode(PreviewMode::Image, con), 382 Internal::preview_text => self.set_mode(PreviewMode::Text, con), 383 Internal::preview_binary => self.set_mode(PreviewMode::Hex, con), 384 _ => self.on_internal_generic( 385 w, 386 internal_exec, 387 input_invocation, 388 trigger_type, 389 app_state, 390 cc, 391 ), 392 } 393 } 394 get_flags(&self) -> Vec<Flag>395 fn get_flags(&self) -> Vec<Flag> { 396 vec![] 397 } 398 get_starting_input(&self) -> String399 fn get_starting_input(&self) -> String { 400 if let Some(preview) = &self.filtered_preview { 401 preview.pattern().raw 402 } else { 403 self.pending_pattern.raw.clone() 404 } 405 } 406 407 } 408