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