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}