07_file_browser/
07_file_browser.rs1use crossterm::event::KeyCode;
8use crossterm::style::Color;
9use std::fs;
10use std::path::PathBuf;
11use telex::prelude::*;
12
13telex::require_api!(0, 2);
14
15fn main() {
16 telex::run_with_theme(App, telex::theme::Theme::nord()).unwrap();
17}
18
19struct App;
20
21fn list_directory(path: &PathBuf) -> Vec<String> {
22 let mut entries = Vec::new();
23
24 if path.parent().is_some() {
26 entries.push("..".to_string());
27 }
28
29 if let Ok(read_dir) = fs::read_dir(path) {
30 let mut items: Vec<_> = read_dir
31 .filter_map(|e| e.ok())
32 .map(|e| {
33 let name = e.file_name().to_string_lossy().to_string();
34 if e.path().is_dir() {
35 format!("{}/", name)
36 } else {
37 name
38 }
39 })
40 .collect();
41 items.sort();
42 entries.extend(items);
43 }
44
45 entries
46}
47
48impl Component for App {
49 fn render(&self, cx: Scope) -> View {
50 let current_path =
51 state!(cx, || std::env::current_dir().unwrap_or_else(|_| PathBuf::from("/")));
52 let selected = state!(cx, || 0usize);
53 let show_file_info = state!(cx, || false);
54 let selected_file_path = state!(cx, String::new);
55 let show_help = state!(cx, || false);
56 let entries = list_directory(¤t_path.get());
57
58 cx.use_command(
60 KeyBinding::key(KeyCode::F(1)),
61 with!(show_help => move || show_help.update(|v| *v = !*v)),
62 );
63
64 let on_select = with!(selected => move |idx: usize| {
66 selected.set(idx);
67 });
68
69 let on_dismiss = with!(show_file_info => move || show_file_info.set(false));
71
72 let entries_for_cmd = entries.clone();
74 cx.use_command(
75 KeyBinding::key(KeyCode::Enter),
76 with!(current_path, selected, show_file_info, selected_file_path => move || {
77 let idx = selected.get();
78 if idx < entries_for_cmd.len() {
79 let entry = &entries_for_cmd[idx];
80 let path = current_path.get();
81
82 if entry == ".." {
83 if let Some(parent) = path.parent() {
84 current_path.set(parent.to_path_buf());
85 selected.set(0);
86 }
87 } else if entry.ends_with('/') {
88 let dir_name = entry.trim_end_matches('/');
89 let new_path = path.join(dir_name);
90 current_path.set(new_path);
91 selected.set(0);
92 } else {
93 let full_path = path.join(entry);
95 selected_file_path.set(full_path.to_string_lossy().to_string());
96 show_file_info.set(true);
97 }
98 }
99 }),
100 );
101
102 let path_display = current_path.get().to_string_lossy().to_string();
103
104 View::vstack()
105 .child(
106 View::styled_text("File Browser")
107 .color(Color::Cyan)
108 .bold()
109 .build(),
110 )
111 .child(View::gap(1))
112 .child(
113 View::styled_text(&path_display)
114 .color(Color::Yellow)
115 .build(),
116 )
117 .child(View::gap(1))
118 .child(
119 View::list()
120 .items(entries)
121 .selected(selected.get())
122 .on_select(on_select)
123 .build(),
124 )
125 .child(View::gap(1))
126 .child(
127 View::styled_text("↑/↓ navigate • Enter open • F1 help • Ctrl+Q quit")
128 .dim()
129 .build(),
130 )
131 .child(
132 View::modal()
133 .visible(show_file_info.get())
134 .title("File")
135 .width(60)
136 .height(20)
137 .on_dismiss(on_dismiss)
138 .child(
139 View::vstack()
140 .child(View::text(selected_file_path.get()))
141 .child(View::gap(1))
142 .child(View::styled_text("Press Escape to close").dim().build())
143 .build(),
144 )
145 .build(),
146 )
147 .child(
148 View::modal()
149 .visible(show_help.get())
150 .title("Example 07: File Browser")
151 .on_dismiss(with!(show_help => move || show_help.set(false)))
152 .child(
153 View::vstack()
154 .child(View::styled_text("What you're seeing").bold().build())
155 .child(View::text("• Real filesystem navigation"))
156 .child(View::text("• cx.use_command() for keyboard shortcuts"))
157 .child(View::text("• Modal for file details"))
158 .child(View::gap(1))
159 .child(View::styled_text("Key concepts").bold().build())
160 .child(View::text("• std::fs::read_dir for directory listing"))
161 .child(View::text(
162 "• KeyBinding::key(KeyCode::Enter) for Enter handling",
163 ))
164 .child(View::text("• Directories shown with trailing /"))
165 .child(View::gap(1))
166 .child(View::styled_text("Try this").bold().build())
167 .child(View::text("• Navigate into directories with Enter"))
168 .child(View::text("• Go up with '..' entry"))
169 .child(View::text("• Select a file to see its path"))
170 .child(View::gap(1))
171 .child(View::styled_text("Next up").bold().build())
172 .child(View::text(
173 "→ 08_system_monitor: multiple concurrent streams",
174 ))
175 .child(View::gap(1))
176 .child(View::styled_text("Press Escape to close").dim().build())
177 .build(),
178 )
179 .build(),
180 )
181 .build()
182 }
183}