fm/app/
previewer.rs

1use std::path::PathBuf;
2use std::sync::mpsc;
3use std::thread;
4
5use anyhow::Result;
6
7use crate::modes::{Preview, PreviewBuilder};
8
9enum RequestKind {
10    PreviewRequest(PreviewRequest),
11    Quit,
12}
13
14struct PreviewRequest {
15    path: PathBuf,
16    tab_index: usize,
17    line_nr: Option<usize>,
18}
19
20impl PreviewRequest {
21    fn new(path: PathBuf, tab_index: usize, line_nr: Option<usize>) -> Self {
22        Self {
23            path,
24            tab_index,
25            line_nr,
26        }
27    }
28}
29
30/// Response from the preview builder with the request data it came from.
31/// The path and tab index are used to ensure we're attaching the correct preview for this tab,
32/// The line number, if any, is used to scroll the the line. Only fuzzy picker of line may output a line number.
33pub struct PreviewResponse {
34    pub path: PathBuf,
35    pub tab_index: usize,
36    pub line_nr: Option<usize>,
37    pub preview: Preview,
38}
39
40impl PreviewResponse {
41    fn new(path: PathBuf, tab_index: usize, line_nr: Option<usize>, preview: Preview) -> Self {
42        Self {
43            path,
44            tab_index,
45            line_nr,
46            preview,
47        }
48    }
49}
50
51/// Non blocking preview builder.
52///
53/// Allow preview building without blocking status.
54/// The process is quite complicated but fast.
55/// We use 2 [`std::sync::mpsc`] :
56/// - one to ask a preview to be built, sent from [`crate::app::Status`] itself, it's received here in a separated thread and built outisde of status thread.
57/// - one to send the [`crate::modes::Preview`] out of the thread to status. It's the responsability of the application to force status to attach the preview.
58///
59/// ATM only the previews for the second pane are built here. It's useless if the user display previews in the current tab since navigation isn't possible.
60pub struct Previewer {
61    tx_request: mpsc::Sender<RequestKind>,
62}
63
64impl Previewer {
65    /// Starts the previewer loop in a thread and create a new instance with a [`std::sync::mpsc::Sender`].
66    ///
67    /// The previewer will wait for [`std::sync::mpsc::Receiver`] messages and react accordingly :
68    /// - if the message asks to quit, it will break the loop and leave.
69    /// - if the message is a request, it will create the associate preview and send it back to the application.
70    ///   The application should then ask the status to attach the preview. It's complicated but I couldn't find a simpler way to check
71    ///   for the preview.
72    pub fn new(tx_preview: mpsc::Sender<PreviewResponse>) -> Self {
73        let (tx_request, rx_request) = mpsc::channel::<RequestKind>();
74        thread::spawn(move || {
75            while let Some(request) = rx_request.iter().next() {
76                match request {
77                    RequestKind::PreviewRequest(PreviewRequest {
78                        path,
79                        tab_index,
80                        line_nr,
81                    }) => {
82                        if let Ok(preview) = PreviewBuilder::new(&path).build() {
83                            tx_preview
84                                .send(PreviewResponse::new(path, tab_index, line_nr, preview))
85                                .unwrap();
86                        };
87                    }
88                    RequestKind::Quit => break,
89                }
90            }
91        });
92        Self { tx_request }
93    }
94
95    /// Sends a "quit" message to the previewer loop. It will break the loop, exiting the previewer.
96    pub fn quit(&self) {
97        crate::log_info!("stopping previewer loop");
98        match self.tx_request.send(RequestKind::Quit) {
99            Ok(()) => (),
100            Err(e) => crate::log_info!("Previewer::quit error {e:?}"),
101        };
102    }
103
104    /// Sends an "ask preview" to the previewer loop. A preview will be built, which won't block the application.
105    /// Once the preview is built, it's send back to status, which should be asked to attach the preview.
106    /// The preview won't be attached automatically, it's the responsability of the application to do it.
107    pub fn build(&self, path: PathBuf, index: usize, line_index: Option<usize>) -> Result<()> {
108        self.tx_request
109            .send(RequestKind::PreviewRequest(PreviewRequest::new(
110                path, index, line_index,
111            )))?;
112        Ok(())
113    }
114}