file_preview/
file_preview.rs1use std::{
2 fs::read_to_string,
3 io::{self, stdout},
4 path::Path,
5};
6
7use crossterm::{
8 event::{read, Event, KeyCode},
9 terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
10 ExecutableCommand,
11};
12use ratatui::{prelude::*, widgets::*};
13
14use copypasta::{ClipboardContext, ClipboardProvider};
15use fpicker::{File, FileExplorer, Theme};
16
17fn main() -> io::Result<()> {
18 enable_raw_mode()?;
19 stdout().execute(EnterAlternateScreen)?;
20
21 let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?;
22 let layout = Layout::vertical([
23 Constraint::Ratio(4, 5), Constraint::Ratio(1, 5), ]);
26
27 let top_layout = Layout::horizontal([Constraint::Ratio(1, 3), Constraint::Ratio(2, 3)]);
29
30 let theme = get_theme();
32 let mut file_explorer = FileExplorer::with_theme(theme)?;
33
34 loop {
35 let file_content = get_file_content(file_explorer.current().path())?;
37 let selected_files: String = file_explorer
38 .selected_files()
39 .iter()
40 .map(|file| file.path().display().to_string())
41 .collect::<Vec<String>>()
42 .join("\n");
43
44 terminal.draw(|f| {
46 let chunks = layout.split(f.area());
47 let top_chunks = top_layout.split(chunks[0]);
48
49 f.render_widget(&file_explorer.widget(), top_chunks[0]);
51 f.render_widget(
52 Paragraph::new(file_content).block(
53 Block::default()
54 .borders(Borders::ALL)
55 .border_type(BorderType::Double),
56 ),
57 top_chunks[1],
58 );
59
60 f.render_widget(
62 Paragraph::new(selected_files.clone()).block(
63 Block::default()
64 .borders(Borders::ALL)
65 .title("q:Quit, c: copy selected file contents to clipboard, p: copy paths to clipboard"),
66 ),
67 chunks[1],
68 );
69 })?;
70
71 let event = read()?;
73 if let Event::Key(key) = event {
74 match key.code {
75 KeyCode::Char('q') => break, KeyCode::Char('c') => {
77 if let Ok(mut clipboard) = ClipboardContext::new() {
79 if let Err(err) = clipboard.set_contents(get_selected_files_content(
80 &file_explorer.selected_files(),
81 )) {
82 eprintln!("Failed to copy to clipboard: {}", err);
83 }
84 } else {
85 eprintln!("Clipboard not available.");
86 }
87 }
88 KeyCode::Char('p') => {
89 if let Ok(mut clipboard) = ClipboardContext::new() {
91 if let Err(err) = clipboard.set_contents(selected_files.clone()) {
92 eprintln!("Failed to copy to clipboard: {}", err);
93 }
94 } else {
95 eprintln!("Clipboard not available.");
96 }
97 }
98 _ => {}
99 }
100 }
101 file_explorer.handle(&event)?;
103 }
104
105 disable_raw_mode()?;
106 stdout().execute(LeaveAlternateScreen)?;
107 for file in file_explorer.selected_files() {
109 println!("{}", file.path().display());
110 }
111 Ok(())
112}
113
114fn get_file_content(path: &Path) -> io::Result<String> {
115 let mut content = String::new();
116
117 if path.is_file() {
119 content = read_to_string(path)?;
120 }
121
122 Ok(content)
123}
124fn get_selected_files_content(selected_files: &Vec<File>) -> String {
125 selected_files
126 .iter()
127 .map(|file| {
128 let path = file.path().display().to_string();
129 let content = get_file_content(file.path())
130 .unwrap_or_else(|_| "Unable to read file.".to_string());
131 format!("File: {}\nContent:\n{}\n", path, content)
132 })
133 .collect::<Vec<String>>()
134 .join("\n")
135}
136
137fn get_theme() -> Theme {
138 Theme::default()
139 .with_block(Block::default().borders(Borders::ALL))
140 .with_dir_style(
141 Style::default()
142 .fg(Color::White)
143 .add_modifier(Modifier::BOLD),
144 )
145 .with_highlight_dir_style(
146 Style::default()
147 .fg(Color::White)
148 .add_modifier(Modifier::BOLD)
149 .bg(Color::DarkGray),
150 )
151}