Skip to main content

rusty_rich/
file_proxy.rs

1//! File proxy — auto-refreshing display of file content.
2
3use crate::console::{ConsoleOptions, Renderable, RenderResult};
4use crate::segment::Segment;
5use crate::style::Style;
6use std::fs;
7use std::path::PathBuf;
8use std::time::SystemTime;
9
10/// A renderable that displays the contents of a file and auto-refreshes.
11#[derive(Debug, Clone)]
12pub struct FileProxy {
13    path: PathBuf,
14    content: String,
15    last_modified: Option<SystemTime>,
16    max_lines: Option<usize>,
17    style: Style,
18}
19
20impl FileProxy {
21    pub fn new(path: impl Into<PathBuf>) -> Self {
22        let path = path.into();
23        let content = fs::read_to_string(&path).unwrap_or_default();
24        let last_modified = fs::metadata(&path).ok().and_then(|m| m.modified().ok());
25        Self {
26            path,
27            content,
28            last_modified,
29            max_lines: None,
30            style: Style::new(),
31        }
32    }
33
34    /// Set maximum lines to display.
35    pub fn max_lines(mut self, max: usize) -> Self {
36        self.max_lines = Some(max);
37        self
38    }
39
40    /// Set display style.
41    pub fn style(mut self, style: Style) -> Self {
42        self.style = style;
43        self
44    }
45
46    /// Refresh content from disk.
47    pub fn refresh(&mut self) -> bool {
48        if let Ok(meta) = fs::metadata(&self.path) {
49            if let Ok(modified) = meta.modified() {
50                if self.last_modified.map_or(true, |last| modified > last) {
51                    if let Ok(content) = fs::read_to_string(&self.path) {
52                        self.content = content;
53                        self.last_modified = Some(modified);
54                        return true;
55                    }
56                }
57            }
58        }
59        false
60    }
61
62    /// Get current content.
63    pub fn content(&self) -> &str {
64        &self.content
65    }
66}
67
68impl Renderable for FileProxy {
69    fn render(&self, _options: &ConsoleOptions) -> RenderResult {
70        let lines: Vec<&str> = self.content.lines().collect();
71        let displayed = if let Some(max) = self.max_lines {
72            &lines[..lines.len().min(max)]
73        } else {
74            &lines
75        };
76
77        let seg_lines: Vec<Vec<Segment>> = displayed
78            .iter()
79            .map(|line| vec![Segment::new(line.to_string())])
80            .collect();
81
82        RenderResult {
83            lines: seg_lines,
84            items: Vec::new(),
85        }
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92    use std::io::Write;
93
94    #[test]
95    fn test_file_proxy() {
96        let dir = std::env::temp_dir();
97        let path = dir.join("rusty_rich_test_file_proxy.txt");
98        let mut f = std::fs::File::create(&path).unwrap();
99        write!(f, "line1\nline2\nline3").unwrap();
100        drop(f);
101
102        let proxy = FileProxy::new(&path).max_lines(2);
103        let content = proxy.content();
104        assert!(content.contains("line1"));
105
106        let _ = std::fs::remove_file(&path);
107    }
108}