1use {
2 super::*,
3 crate::{
4 app::*,
5 command::{
6 Command,
7 ScrollCommand,
8 TriggerType,
9 },
10 display::{
11 Screen,
12 W,
13 },
14 errors::ProgramError,
15 flag::Flag,
16 pattern::InputPattern,
17 task_sync::Dam,
18 tree::TreeOptions,
19 verb::*,
20 },
21 crokey::crossterm::{
22 QueueableCommand,
23 cursor,
24 },
25 std::path::{
26 Path,
27 PathBuf,
28 },
29 termimad::{
30 Area,
31 CropWriter,
32 SPACE_FILLING,
33 },
34};
35
36pub struct PreviewState {
41 pub preview_area: Area,
42 dirty: bool, source_path: PathBuf, transform: Option<PreviewTransform>,
45 preview: Preview,
46 pending_pattern: InputPattern, filtered_preview: Option<Preview>,
48 removed_pattern: InputPattern,
49 preferred_mode: Option<PreviewMode>,
50 tree_options: TreeOptions,
51 mode: Mode,
52}
53
54impl PreviewState {
55 pub fn new(
56 source_path: PathBuf,
57 pending_pattern: InputPattern,
58 preferred_mode: Option<PreviewMode>,
59 tree_options: TreeOptions,
60 con: &AppContext,
61 ) -> PreviewState {
62 let preview_area = Area::uninitialized(); let transform = con
64 .preview_transformers
65 .transform(&source_path, preferred_mode);
66 let preview_path = transform
67 .as_ref()
68 .map(|c| &c.output_path)
69 .unwrap_or(&source_path);
70 let preview = Preview::new(preview_path, preferred_mode, con);
71 PreviewState {
72 preview_area,
73 dirty: true,
74 source_path,
75 transform,
76 preview,
77 pending_pattern,
78 filtered_preview: None,
79 removed_pattern: InputPattern::none(),
80 preferred_mode,
81 tree_options,
82 mode: con.initial_mode(),
83 }
84 }
85 pub fn preview_path(&self) -> &Path {
86 self.transform
87 .as_ref()
88 .map(|c| &c.output_path)
89 .unwrap_or(&self.source_path)
90 }
91 fn vis_preview(&self) -> &Preview {
92 self.filtered_preview.as_ref().unwrap_or(&self.preview)
93 }
94 fn mut_preview(&mut self) -> &mut Preview {
95 self.filtered_preview.as_mut().unwrap_or(&mut self.preview)
96 }
97 fn set_mode(
98 &mut self,
99 mode: PreviewMode,
100 con: &AppContext,
101 ) -> Result<CmdResult, ProgramError> {
102 if self.preview.get_mode() == Some(mode) {
103 return Ok(CmdResult::Keep);
104 }
105 Ok(match Preview::with_mode(self.preview_path(), mode, con) {
106 Ok(preview) => {
107 self.preview = preview;
108 self.preferred_mode = Some(mode);
109 CmdResult::Keep
110 }
111 Err(e) => CmdResult::DisplayError(format!("Can't display as {mode:?} : {e:?}")),
112 })
113 }
114
115 fn no_opt_selection(&self) -> Selection<'_> {
116 match self.transform.as_ref() {
117 Some(transform) => Selection {
119 path: &transform.output_path,
120 stype: SelectionType::File,
121 is_exe: false,
122 line: 0,
123 },
124 None => Selection {
125 path: &self.source_path,
126 stype: SelectionType::File,
127 is_exe: false,
128 line: self.vis_preview().get_selected_line_number().unwrap_or(0),
129 },
130 }
131 }
132
133 fn do_pending_search(
135 &mut self,
136 con: &AppContext,
137 dam: &mut Dam,
138 ) -> Result<(), ProgramError> {
139 let old_selection = self
140 .filtered_preview
141 .as_ref()
142 .and_then(|p| p.get_selected_line_number())
143 .or_else(|| self.preview.get_selected_line_number());
144 let pattern = self.pending_pattern.take();
145 self.filtered_preview = time!(
146 Info,
147 "preview filtering",
148 self.preview
149 .filtered(self.preview_path(), pattern, dam, con),
150 ); if let Some(ref mut filtered_preview) = self.filtered_preview {
152 if let Some(number) = old_selection {
153 filtered_preview.try_select_line_number(number);
154 }
155 }
156 Ok(())
157 }
158}
159
160impl PanelState for PreviewState {
161 fn get_type(&self) -> PanelStateType {
162 PanelStateType::Preview
163 }
164
165 fn set_mode(
166 &mut self,
167 mode: Mode,
168 ) {
169 self.mode = mode;
170 }
171
172 fn get_mode(&self) -> Mode {
173 self.mode
174 }
175
176 fn get_pending_task(&self) -> Option<&'static str> {
177 if self.preview.is_partial() {
178 Some("loading")
179 } else if self.pending_pattern.is_some() {
180 Some("searching")
181 } else {
182 None
183 }
184 }
185
186 fn on_pattern(
187 &mut self,
188 pat: InputPattern,
189 _app_state: &AppState,
190 _con: &AppContext,
191 ) -> Result<CmdResult, ProgramError> {
192 if pat.is_none() {
193 if let Some(filtered_preview) = self.filtered_preview.take() {
194 let old_selection = filtered_preview.get_selected_line_number();
195 if let Some(number) = old_selection {
196 self.preview.try_select_line_number(number);
197 }
198 self.removed_pattern = filtered_preview.pattern();
199 }
200 } else if !self.preview.is_filterable() {
201 return Ok(CmdResult::error("this preview can't be searched"));
202 }
203 self.pending_pattern = pat;
204 Ok(CmdResult::Keep)
205 }
206
207 fn do_pending_task(
208 &mut self,
209 _app_state: &mut AppState,
210 _screen: Screen,
211 con: &AppContext,
212 dam: &mut Dam,
213 ) -> Result<(), ProgramError> {
214 if self.preview.is_partial() {
215 self.preview.complete_loading(con, dam)?;
216 } else if self.pending_pattern.is_some() {
217 self.do_pending_search(con, dam)?;
218 }
219 Ok(())
220 }
221
222 fn selected_path(&self) -> Option<&Path> {
223 Some(&self.source_path)
224 }
225
226 fn set_selected_path(
227 &mut self,
228 path: PathBuf,
229 con: &AppContext,
230 ) {
231 let selected_line_number = if self.preview_path() == path {
232 self.preview.get_selected_line_number()
233 } else {
234 None
235 };
236 if let Some(fp) = &self.filtered_preview {
237 self.pending_pattern = fp.pattern();
238 };
239 self.transform = con
240 .preview_transformers
241 .transform(&path, self.preferred_mode);
242 let preview_path = self.transform.as_ref().map_or(&path, |c| &c.output_path);
243 self.preview = Preview::new(preview_path, self.preferred_mode, con);
244 if let Some(number) = selected_line_number {
245 self.preview.try_select_line_number(number);
246 }
247 self.source_path = path;
248 }
249
250 fn selection(&self) -> Option<Selection<'_>> {
251 Some(self.no_opt_selection())
252 }
253
254 fn tree_options(&self) -> TreeOptions {
255 self.tree_options.clone()
256 }
257
258 fn with_new_options(
259 &mut self,
260 _screen: Screen,
261 change_options: &dyn Fn(&mut TreeOptions) -> &'static str,
262 _in_new_panel: bool, _con: &AppContext,
264 ) -> CmdResult {
265 change_options(&mut self.tree_options);
266 CmdResult::Keep
267 }
268
269 fn refresh(
270 &mut self,
271 _screen: Screen,
272 con: &AppContext,
273 ) -> Command {
274 self.dirty = true;
275 self.set_selected_path(self.source_path.clone(), con);
276 Command::empty()
277 }
278
279 fn on_click(
280 &mut self,
281 _x: u16,
282 y: u16,
283 _screen: Screen,
284 _con: &AppContext,
285 ) -> Result<CmdResult, ProgramError> {
286 if y >= self.preview_area.top && y < self.preview_area.top + self.preview_area.height {
287 let y = y - self.preview_area.top;
288 self.mut_preview().try_select_y(y);
289 }
290 Ok(CmdResult::Keep)
291 }
292
293 fn display(
294 &mut self,
295 w: &mut W,
296 disc: &DisplayContext,
297 ) -> Result<(), ProgramError> {
298 let state_area = &disc.state_area;
299 if state_area.height < 3 {
300 warn!("area too small for preview");
301 return Ok(());
302 }
303 let mut preview_area = state_area.clone();
304 preview_area.height -= 1;
305 preview_area.top += 1;
306 if preview_area != self.preview_area {
307 self.dirty = true;
308 self.preview_area = preview_area;
309 }
310 if self.dirty {
311 disc.panel_skin.styles.default.queue_bg(w)?;
312 disc.screen.clear_area_to_right(w, state_area)?;
313 self.dirty = false;
314 }
315 let styles = &disc.panel_skin.styles;
316 w.queue(cursor::MoveTo(state_area.left, 0))?;
317 let mut cw = CropWriter::new(w, state_area.width as usize);
318 let file_name = self
319 .source_path
320 .file_name()
321 .map(|n| n.to_string_lossy().to_string())
322 .unwrap_or_else(|| "???".to_string());
323 cw.queue_str(&styles.preview_title, &file_name)?;
324 let info_area = Area::new(
325 state_area.left + state_area.width - cw.allowed as u16,
326 state_area.top,
327 cw.allowed as u16,
328 1,
329 );
330 cw.fill(&styles.preview_title, &SPACE_FILLING)?;
331 let preview = self.filtered_preview.as_mut().unwrap_or(&mut self.preview);
332 preview.display_info(w, disc.screen, disc.panel_skin, &info_area)?;
333 if let Err(err) = preview.display(w, disc, &self.preview_area) {
334 warn!("error while displaying file: {:?}", &err);
335 if preview.get_mode().is_some() {
336 if let ProgramError::Io { source } = err {
338 self.preview = Preview::IoError(source);
340 return self.display(w, disc);
341 }
342 }
343 return Err(err);
344 }
345 Ok(())
346 }
347
348 fn no_verb_status(
349 &self,
350 has_previous_state: bool,
351 con: &AppContext,
352 width: usize, ) -> Status {
354 let mut ssb =
355 con.standard_status
356 .builder(PanelStateType::Preview, self.no_opt_selection(), width);
357 ssb.has_previous_state = has_previous_state;
358 ssb.is_filtered = self.filtered_preview.is_some();
359 ssb.has_removed_pattern = self.removed_pattern.is_some();
360 ssb.status()
361 }
362
363 fn on_internal(
364 &mut self,
365 w: &mut W,
366 invocation_parser: Option<&InvocationParser>,
367 internal_exec: &InternalExecution,
368 input_invocation: Option<&VerbInvocation>,
369 trigger_type: TriggerType,
370 app_state: &mut AppState,
371 cc: &CmdContext,
372 ) -> Result<CmdResult, ProgramError> {
373 let con = &cc.app.con;
374 match internal_exec.internal {
375 Internal::back => {
376 if self.filtered_preview.is_some() {
377 self.on_pattern(InputPattern::none(), app_state, con)
378 } else {
379 Ok(CmdResult::PopState)
380 }
381 }
382 Internal::copy_line => {
383 #[cfg(not(feature = "clipboard"))]
384 {
385 Ok(CmdResult::error(
386 "Clipboard feature not enabled at compilation",
387 ))
388 }
389 #[cfg(feature = "clipboard")]
390 {
391 Ok(match self.mut_preview().get_selected_line() {
392 Some(line) => match terminal_clipboard::set_string(line) {
393 Ok(()) => CmdResult::Keep,
394 Err(_) => CmdResult::error("Clipboard error while copying path"),
395 },
396 None => CmdResult::error("No selected line in preview"),
397 })
398 }
399 }
400 Internal::line_down => {
401 let count = get_arg(input_invocation, internal_exec, 1);
402 self.mut_preview().move_selection(count, true);
403 Ok(CmdResult::Keep)
404 }
405 Internal::line_up => {
406 let count = get_arg(input_invocation, internal_exec, 1);
407 self.mut_preview().move_selection(-count, true);
408 Ok(CmdResult::Keep)
409 }
410 Internal::line_down_no_cycle => {
411 let count = get_arg(input_invocation, internal_exec, 1);
412 self.mut_preview().move_selection(count, false);
413 Ok(CmdResult::Keep)
414 }
415 Internal::line_up_no_cycle => {
416 let count = get_arg(input_invocation, internal_exec, 1);
417 self.mut_preview().move_selection(-count, false);
418 Ok(CmdResult::Keep)
419 }
420 Internal::page_down => {
421 self.mut_preview().try_scroll(ScrollCommand::Pages(1));
422 Ok(CmdResult::Keep)
423 }
424 Internal::page_up => {
425 self.mut_preview().try_scroll(ScrollCommand::Pages(-1));
426 Ok(CmdResult::Keep)
427 }
428 Internal::panel_left if self.removed_pattern.is_some() => {
434 self.pending_pattern = self.removed_pattern.take();
435 Ok(CmdResult::Keep)
436 }
437 Internal::panel_left_no_open if self.removed_pattern.is_some() => {
438 self.pending_pattern = self.removed_pattern.take();
439 Ok(CmdResult::Keep)
440 }
441 Internal::panel_right if self.filtered_preview.is_some() => {
442 self.on_pattern(InputPattern::none(), app_state, con)
443 }
444 Internal::panel_right_no_open if self.filtered_preview.is_some() => {
445 self.on_pattern(InputPattern::none(), app_state, con)
446 }
447 Internal::select_first => {
448 self.mut_preview().select_first();
449 Ok(CmdResult::Keep)
450 }
451 Internal::select_last => {
452 self.mut_preview().select_last();
453 Ok(CmdResult::Keep)
454 }
455 Internal::previous_match => {
456 self.mut_preview().previous_match();
457 Ok(CmdResult::Keep)
458 }
459 Internal::next_match => {
460 self.mut_preview().next_match();
461 Ok(CmdResult::Keep)
462 }
463 Internal::preview_image => self.set_mode(PreviewMode::Image, con),
464 Internal::preview_text => self.set_mode(PreviewMode::Text, con),
465 Internal::preview_tty => self.set_mode(PreviewMode::Tty, con),
466 Internal::preview_binary => self.set_mode(PreviewMode::Hex, con),
467 _ => self.on_internal_generic(
468 w,
469 invocation_parser,
470 internal_exec,
471 input_invocation,
472 trigger_type,
473 app_state,
474 cc,
475 ),
476 }
477 }
478
479 fn get_flags(&self) -> Vec<Flag> {
480 vec![]
481 }
482
483 fn get_starting_input(&self) -> String {
484 if let Some(preview) = &self.filtered_preview {
485 preview.pattern().raw
486 } else {
487 self.pending_pattern.raw.clone()
488 }
489 }
490}