file_reader/
file_reader.rs

1use rustui::*;
2use std::{
3    fs::File,
4    io::{self, BufRead, BufReader},
5    thread, time,
6};
7
8const RENDERING_RATE: time::Duration = time::Duration::from_millis(31); // ms
9const INPUT_CAPTURING_RATE: time::Duration = time::Duration::from_millis(10); // ms
10
11fn main() -> Result<(), Box<dyn std::error::Error>> {
12    let args: Vec<String> = std::env::args().collect();
13    let file_path = if args.len() > 1 {
14        &args[1]
15    } else {
16        eprintln!("Usage: file_reader <file_path>");
17        return Ok(());
18    };
19
20    let mut file_reader = FileReader::new(file_path);
21
22    let mut win = Window::new(false)?;
23    win.initialize(RENDERING_RATE)?; // Initialize the window and start the rendering thread
24    let input_rx = InputListener::new(INPUT_CAPTURING_RATE); // Create an input listener
25
26    loop {
27        // Check for key presses
28        if let Ok(event) = input_rx.try_recv() {
29            match event {
30                InputEvent::Key(Key::Char('q')) => break,
31                InputEvent::Key(Key::ArrowUp) => {
32                    file_reader.scroll_up();
33                }
34                InputEvent::Key(Key::ArrowDown) => {
35                    let visible_lines = win.height.saturating_sub(4);
36                    file_reader.scroll_down(visible_lines);
37                }
38                InputEvent::Key(Key::PageUp) => {
39                    let visible_lines = win.height.saturating_sub(4);
40                    file_reader.page_up(visible_lines);
41                }
42                InputEvent::Key(Key::PageDown) => {
43                    let visible_lines = win.height.saturating_sub(4);
44                    file_reader.page_down(visible_lines);
45                }
46                InputEvent::Key(Key::Home) => {
47                    file_reader.current_line = 0;
48                    file_reader.scroll_offset = 0;
49                }
50                InputEvent::Key(Key::End) => {
51                    let visible_lines = win.height.saturating_sub(4);
52                    file_reader.current_line = file_reader.lines.len().saturating_sub(1);
53                    file_reader.scroll_offset =
54                        file_reader.lines.len().saturating_sub(visible_lines);
55                }
56                _ => {}
57            }
58        }
59
60        // Draw the frame
61        win.draw(|canvas| {
62            file_reader.render(canvas);
63        })?;
64
65        thread::sleep(time::Duration::from_millis(31)); // Sleep to prevent high CPU usage
66    }
67    Ok(())
68}
69
70struct FileReader {
71    file_path: String,
72    lines: Vec<String>,
73    current_line: usize,
74    scroll_offset: usize,
75}
76
77impl FileReader {
78    fn new(file_path: &str) -> Self {
79        let file = File::open(file_path).unwrap();
80        let reader = BufReader::new(file);
81        let lines: Result<Vec<String>, io::Error> = reader.lines().collect();
82        Self {
83            file_path: file_path.to_string(),
84            lines: lines.unwrap(),
85            current_line: 0,
86            scroll_offset: 0,
87        }
88    }
89
90    fn scroll_up(&mut self) {
91        if self.current_line > 0 {
92            self.current_line -= 1;
93            if self.current_line < self.scroll_offset {
94                self.scroll_offset = self.current_line;
95            }
96        }
97    }
98
99    fn scroll_down(&mut self, visible_lines: usize) {
100        if self.current_line + 1 < self.lines.len() {
101            self.current_line += 1;
102            if self.current_line >= self.scroll_offset + visible_lines {
103                self.scroll_offset = self.current_line - visible_lines + 1;
104            }
105        }
106    }
107
108    fn page_up(&mut self, visible_lines: usize) {
109        let page_size = visible_lines.saturating_sub(1);
110        self.current_line = self.current_line.saturating_sub(page_size);
111        self.scroll_offset = self.scroll_offset.saturating_sub(page_size);
112    }
113
114    fn page_down(&mut self, visible_lines: usize) {
115        let page_size = visible_lines.saturating_sub(1);
116        self.current_line = (self.current_line + page_size).min(self.lines.len().saturating_sub(1));
117
118        if self.current_line >= self.scroll_offset + visible_lines {
119            self.scroll_offset = self.current_line - visible_lines + 1;
120        }
121    }
122
123    fn render(&self, canvas: &mut Framebuffer) {
124        // ボーダーを設定(ファイル名を表示)
125        canvas.set_named_border(
126            &format!("File Reader - {}", self.file_path),
127            Align::Center,
128            Attr::NORMAL,
129            Color::White,
130            Color::default(),
131        );
132
133        // コンテンツ表示領域のサイズを計算(ボーダーを除く)
134        let content_width = canvas.width.saturating_sub(4);
135        let content_height = canvas.height.saturating_sub(4);
136        let start_x = 2;
137        let start_y = 2;
138
139        // ファイルの内容を表示
140        for (i, line_idx) in (self.scroll_offset..self.scroll_offset + content_height).enumerate() {
141            if line_idx >= self.lines.len() {
142                break;
143            }
144
145            let line = &self.lines[line_idx];
146            let is_current = line_idx == self.current_line;
147
148            // 現在行のハイライト
149            let (attrs, fg, bg) = if is_current {
150                (Attr::NORMAL, Color::Black, Color::White)
151            } else {
152                (Attr::NORMAL, Color::White, Color::default())
153            };
154
155            // 行番号を表示
156            let line_number = format!("{:4} ", line_idx + 1);
157            canvas.set_str(
158                start_x,
159                start_y + i,
160                &line_number,
161                attrs,
162                Color::Cyan,
163                bg,
164                Align::Left,
165            );
166
167            // ファイルの内容を表示(長い行は切り詰める)
168            let display_line = if line.len() > content_width - 5 {
169                format!("{}...", &line[..content_width - 8])
170            } else {
171                line.clone()
172            };
173
174            canvas.set_str(
175                start_x + 5,
176                start_y + i,
177                &display_line,
178                attrs,
179                fg,
180                bg,
181                Align::Left,
182            );
183        }
184
185        // ステータスバーを表示
186        let status = format!(
187            "Line {}/{} | Press ↑↓ to scroll, PgUp/PgDn for pages, 'q' to quit",
188            self.current_line + 1,
189            self.lines.len()
190        );
191
192        canvas.set_str(
193            canvas.width / 2,
194            canvas.height - 1,
195            &status,
196            Attr::NORMAL,
197            Color::Yellow,
198            Color::default(),
199            Align::Center,
200        );
201    }
202}