television_previewers/
previewers.rs

1use std::sync::Arc;
2
3use devicons::FileIcon;
4use television_channels::entry::{Entry, PreviewType};
5
6pub mod basic;
7pub mod cache;
8pub mod command;
9pub mod env;
10pub mod files;
11pub mod meta;
12
13// previewer types
14pub use basic::BasicPreviewer;
15pub use basic::BasicPreviewerConfig;
16pub use command::CommandPreviewer;
17pub use command::CommandPreviewerConfig;
18pub use env::EnvVarPreviewer;
19pub use env::EnvVarPreviewerConfig;
20pub use files::FilePreviewer;
21pub use files::FilePreviewerConfig;
22use television_utils::cache::RingSet;
23use television_utils::syntax::HighlightedLines;
24
25#[derive(Clone, Debug)]
26pub enum PreviewContent {
27    Empty,
28    FileTooLarge,
29    SyntectHighlightedText(HighlightedLines),
30    Loading,
31    Timeout,
32    NotSupported,
33    PlainText(Vec<String>),
34    PlainTextWrapped(String),
35    AnsiText(String),
36}
37
38impl PreviewContent {
39    pub fn total_lines(&self) -> u16 {
40        match self {
41            PreviewContent::SyntectHighlightedText(hl_lines) => {
42                hl_lines.lines.len().try_into().unwrap_or(u16::MAX)
43            }
44            PreviewContent::PlainText(lines) => {
45                lines.len().try_into().unwrap_or(u16::MAX)
46            }
47            PreviewContent::AnsiText(text) => {
48                text.lines().count().try_into().unwrap_or(u16::MAX)
49            }
50            _ => 0,
51        }
52    }
53}
54
55pub const PREVIEW_NOT_SUPPORTED_MSG: &str =
56    "Preview for this file type is not supported";
57pub const FILE_TOO_LARGE_MSG: &str = "File too large";
58pub const LOADING_MSG: &str = "Loading...";
59pub const TIMEOUT_MSG: &str = "Preview timed out";
60
61/// A preview of an entry.
62///
63/// # Fields
64/// - `title`: The title of the preview.
65/// - `content`: The content of the preview.
66#[derive(Clone, Debug)]
67pub struct Preview {
68    pub title: String,
69    pub content: PreviewContent,
70    pub icon: Option<FileIcon>,
71    /// If the preview is partial, this field contains the byte offset
72    /// up to which the preview holds.
73    pub partial_offset: Option<usize>,
74    pub total_lines: u16,
75}
76
77impl Default for Preview {
78    fn default() -> Self {
79        Preview {
80            title: String::new(),
81            content: PreviewContent::Empty,
82            icon: None,
83            partial_offset: None,
84            total_lines: 0,
85        }
86    }
87}
88
89impl Preview {
90    pub fn new(
91        title: String,
92        content: PreviewContent,
93        icon: Option<FileIcon>,
94        partial_offset: Option<usize>,
95        total_lines: u16,
96    ) -> Self {
97        Preview {
98            title,
99            content,
100            icon,
101            partial_offset,
102            total_lines,
103        }
104    }
105
106    pub fn total_lines(&self) -> u16 {
107        match &self.content {
108            PreviewContent::SyntectHighlightedText(hl_lines) => {
109                hl_lines.lines.len().try_into().unwrap_or(u16::MAX)
110            }
111            PreviewContent::PlainText(lines) => {
112                lines.len().try_into().unwrap_or(u16::MAX)
113            }
114            PreviewContent::AnsiText(text) => {
115                text.lines().count().try_into().unwrap_or(u16::MAX)
116            }
117            _ => 0,
118        }
119    }
120}
121
122#[derive(Debug, Default)]
123pub struct Previewer {
124    basic: BasicPreviewer,
125    file: FilePreviewer,
126    env_var: EnvVarPreviewer,
127    command: CommandPreviewer,
128    requests: RingSet<Entry>,
129}
130
131#[derive(Debug, Default)]
132pub struct PreviewerConfig {
133    basic: BasicPreviewerConfig,
134    file: FilePreviewerConfig,
135    env_var: EnvVarPreviewerConfig,
136    command: CommandPreviewerConfig,
137}
138
139impl PreviewerConfig {
140    pub fn basic(mut self, config: BasicPreviewerConfig) -> Self {
141        self.basic = config;
142        self
143    }
144
145    pub fn file(mut self, config: FilePreviewerConfig) -> Self {
146        self.file = config;
147        self
148    }
149
150    pub fn env_var(mut self, config: EnvVarPreviewerConfig) -> Self {
151        self.env_var = config;
152        self
153    }
154}
155
156const REQUEST_STACK_SIZE: usize = 20;
157
158impl Previewer {
159    pub fn new(config: Option<PreviewerConfig>) -> Self {
160        let config = config.unwrap_or_default();
161        Previewer {
162            basic: BasicPreviewer::new(Some(config.basic)),
163            file: FilePreviewer::new(Some(config.file)),
164            env_var: EnvVarPreviewer::new(Some(config.env_var)),
165            command: CommandPreviewer::new(Some(config.command)),
166            requests: RingSet::with_capacity(REQUEST_STACK_SIZE),
167        }
168    }
169
170    fn dispatch_request(&mut self, entry: &Entry) -> Option<Arc<Preview>> {
171        match &entry.preview_type {
172            PreviewType::Basic => Some(self.basic.preview(entry)),
173            PreviewType::EnvVar => Some(self.env_var.preview(entry)),
174            PreviewType::Files => self.file.preview(entry),
175            PreviewType::Command(cmd) => self.command.preview(entry, cmd),
176            PreviewType::None => Some(Arc::new(Preview::default())),
177        }
178    }
179
180    fn cached(&self, entry: &Entry) -> Option<Arc<Preview>> {
181        match &entry.preview_type {
182            PreviewType::Files => self.file.cached(entry),
183            PreviewType::Command(_) => self.command.cached(entry),
184            PreviewType::Basic | PreviewType::EnvVar => None,
185            PreviewType::None => Some(Arc::new(Preview::default())),
186        }
187    }
188
189    pub fn preview(&mut self, entry: &Entry) -> Option<Arc<Preview>> {
190        // if we haven't acknowledged the request yet, acknowledge it
191        self.requests.push(entry.clone());
192
193        if let Some(preview) = self.dispatch_request(entry) {
194            return Some(preview);
195        }
196        // lookup request stack and return the most recent preview available
197        for request in self.requests.back_to_front() {
198            if let Some(preview) = self.cached(&request) {
199                return Some(preview);
200            }
201        }
202        None
203    }
204
205    pub fn set_config(&mut self, config: PreviewerConfig) {
206        self.basic = BasicPreviewer::new(Some(config.basic));
207        self.file = FilePreviewer::new(Some(config.file));
208        self.env_var = EnvVarPreviewer::new(Some(config.env_var));
209    }
210}