Skip to content

Commit 955e977

Browse files
committed
fix: critical directory navigation bug and replace magic panel numbers
- Fix critical bug where directory navigation used truncated display names - Store original file paths separately for accurate navigation - Replace magic panel numbers with type-safe Panel enum - Improve code maintainability and prevent future bugs
1 parent 3f4c637 commit 955e977

File tree

2 files changed

+112
-54
lines changed

2 files changed

+112
-54
lines changed

src/app.rs

Lines changed: 107 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,34 @@ use crate::{
1919
},
2020
};
2121

22+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23+
pub enum Panel {
24+
SystemMonitor = 0,
25+
SystemStatus = 1,
26+
ProcessManager = 2,
27+
FileExplorer = 3,
28+
NetworkGraph = 4,
29+
}
30+
31+
impl Panel {
32+
pub const COUNT: usize = 5;
33+
34+
pub fn from_index(index: usize) -> Option<Panel> {
35+
match index {
36+
0 => Some(Panel::SystemMonitor),
37+
1 => Some(Panel::SystemStatus),
38+
2 => Some(Panel::ProcessManager),
39+
3 => Some(Panel::FileExplorer),
40+
4 => Some(Panel::NetworkGraph),
41+
_ => None,
42+
}
43+
}
44+
45+
pub fn as_index(self) -> usize {
46+
self as usize
47+
}
48+
}
49+
2250
// Cached data structures
2351
#[derive(Clone)]
2452
pub struct CachedProcess {
@@ -41,9 +69,10 @@ pub struct App {
4169
pub networks: Networks,
4270
pub last_update: Instant,
4371
pub last_manual_refresh: Instant,
44-
pub selected_panel: usize,
72+
pub selected_panel: Panel,
4573
pub current_dir: PathBuf,
4674
pub dir_entries: Vec<String>,
75+
pub dir_entry_paths: Vec<PathBuf>, // Store original paths for navigation
4776
pub selected_process: usize,
4877
pub selected_file: usize,
4978
pub selected_network: usize,
@@ -68,7 +97,7 @@ impl App {
6897
warn!("Failed to get current directory: {}, using '.'", e);
6998
PathBuf::from(".")
7099
});
71-
let dir_entries = Self::read_directory(&current_dir);
100+
let (dir_entries, dir_entry_paths) = Self::read_directory(&current_dir);
72101

73102
let mut process_list_state = ListState::default();
74103
process_list_state.select(Some(0));
@@ -84,9 +113,10 @@ impl App {
84113
networks,
85114
last_update: Instant::now(),
86115
last_manual_refresh: Instant::now(),
87-
selected_panel: 0,
116+
selected_panel: Panel::SystemMonitor,
88117
current_dir,
89118
dir_entries,
119+
dir_entry_paths,
90120
selected_process: 0,
91121
selected_file: 0,
92122
selected_network: 0,
@@ -104,33 +134,52 @@ impl App {
104134
app
105135
}
106136

107-
fn read_directory(path: &PathBuf) -> Vec<String> {
137+
fn read_directory(path: &PathBuf) -> (Vec<String>, Vec<PathBuf>) {
108138
match fs::read_dir(path) {
109139
Ok(entries) => {
110140
let mut items = vec!["..".to_string()];
141+
let mut paths = vec![path.parent().unwrap_or(path).to_path_buf()]; // Parent path for ".."
111142
let mut dirs = Vec::new();
143+
let mut dir_paths = Vec::new();
112144
let mut files = Vec::new();
145+
let mut file_paths = Vec::new();
113146

114-
for entry in entries.flatten().take(MAX_FILES) { // Limit to 10000 entries
147+
for entry in entries.flatten().take(MAX_FILES) {
148+
let entry_path = entry.path();
115149
let name = entry.file_name().to_string_lossy().to_string();
116-
// Truncate very long file/directory names to prevent layout issues
117150
let truncated_name = truncate_string(&name, FILE_NAME_MAX_LEN);
118-
if entry.path().is_dir() {
151+
152+
if entry_path.is_dir() {
119153
dirs.push(format!("📁 {}", truncated_name));
154+
dir_paths.push(entry_path);
120155
} else {
121156
files.push(format!("📄 {}", truncated_name));
157+
file_paths.push(entry_path);
122158
}
123159
}
124160

125-
dirs.sort();
126-
files.sort();
127-
items.extend(dirs);
128-
items.extend(files);
129-
items
161+
// Sort directories and files together with their paths
162+
let mut combined: Vec<_> = dirs.into_iter().zip(dir_paths.into_iter()).collect();
163+
combined.sort_by(|a, b| a.0.cmp(&b.0));
164+
165+
let mut file_combined: Vec<_> = files.into_iter().zip(file_paths.into_iter()).collect();
166+
file_combined.sort_by(|a, b| a.0.cmp(&b.0));
167+
168+
// Extract sorted items and paths
169+
for (item, path) in combined {
170+
items.push(item);
171+
paths.push(path);
172+
}
173+
for (item, path) in file_combined {
174+
items.push(item);
175+
paths.push(path);
176+
}
177+
178+
(items, paths)
130179
}
131180
Err(e) => {
132181
error!("Failed to read directory {:?}: {}", path, e);
133-
vec![format!("<Error: {}>", e)]
182+
(vec![format!("<Error: {}>", e)], vec![path.clone()])
134183
},
135184
}
136185
}
@@ -207,19 +256,19 @@ impl App {
207256
}
208257
KeyCode::Up | KeyCode::Char('k') => {
209258
match self.selected_panel {
210-
2 => { // Process manager
259+
Panel::ProcessManager => { // Process manager
211260
if self.selected_process > 0 {
212261
self.selected_process -= 1;
213262
self.process_list_state.select(Some(self.selected_process));
214263
}
215264
}
216-
3 => { // File browser
265+
Panel::FileExplorer => { // File browser
217266
if self.selected_file > 0 {
218267
self.selected_file -= 1;
219268
self.file_list_state.select(Some(self.selected_file));
220269
}
221270
}
222-
4 => { // Network panel - cycle to previous interface
271+
Panel::NetworkGraph => { // Network panel - cycle to previous interface
223272
let network_count = self.cached_networks.len();
224273
if network_count > 0 {
225274
self.selected_network = if self.selected_network == 0 {
@@ -236,20 +285,20 @@ impl App {
236285
}
237286
KeyCode::Down | KeyCode::Char('j') => {
238287
match self.selected_panel {
239-
2 => { // Process manager
288+
Panel::ProcessManager => { // Process manager
240289
let max_processes = self.cached_processes.len();
241290
if self.selected_process < max_processes.saturating_sub(1) {
242291
self.selected_process += 1;
243292
self.process_list_state.select(Some(self.selected_process));
244293
}
245294
}
246-
3 => { // File browser
295+
Panel::FileExplorer => { // File browser
247296
if self.selected_file < self.dir_entries.len() - 1 {
248297
self.selected_file += 1;
249298
self.file_list_state.select(Some(self.selected_file));
250299
}
251300
}
252-
4 => { // Network panel - cycle to next interface
301+
Panel::NetworkGraph => { // Network panel - cycle to next interface
253302
let network_count = self.cached_networks.len();
254303
if network_count > 0 {
255304
self.selected_network = (self.selected_network + 1) % network_count;
@@ -262,12 +311,12 @@ impl App {
262311
}
263312
KeyCode::PageUp => {
264313
match self.selected_panel {
265-
2 => { // Process manager
314+
Panel::ProcessManager => { // Process manager
266315
let page_size = PAGE_SIZE; // Approximate visible items per page
267316
self.selected_process = self.selected_process.saturating_sub(page_size);
268317
self.process_list_state.select(Some(self.selected_process));
269318
}
270-
3 => { // File browser
319+
Panel::FileExplorer => { // File browser
271320
let page_size = PAGE_SIZE;
272321
self.selected_file = self.selected_file.saturating_sub(page_size);
273322
self.file_list_state.select(Some(self.selected_file));
@@ -277,13 +326,13 @@ impl App {
277326
}
278327
KeyCode::PageDown => {
279328
match self.selected_panel {
280-
2 => { // Process manager
329+
Panel::ProcessManager => { // Process manager
281330
let page_size = PAGE_SIZE;
282331
let max_processes = self.cached_processes.len();
283332
self.selected_process = (self.selected_process + page_size).min(max_processes.saturating_sub(1));
284333
self.process_list_state.select(Some(self.selected_process));
285334
}
286-
3 => { // File browser
335+
Panel::FileExplorer => { // File browser
287336
let page_size = PAGE_SIZE;
288337
let max_files = self.dir_entries.len();
289338
self.selected_file = (self.selected_file + page_size).min(max_files.saturating_sub(1));
@@ -294,11 +343,11 @@ impl App {
294343
}
295344
KeyCode::Home => {
296345
match self.selected_panel {
297-
2 => { // Process manager
346+
Panel::ProcessManager => { // Process manager
298347
self.selected_process = 0;
299348
self.process_list_state.select(Some(0));
300349
}
301-
3 => { // File browser
350+
Panel::FileExplorer => { // File browser
302351
self.selected_file = 0;
303352
self.file_list_state.select(Some(0));
304353
}
@@ -307,14 +356,14 @@ impl App {
307356
}
308357
KeyCode::End => {
309358
match self.selected_panel {
310-
2 => { // Process manager
359+
Panel::ProcessManager => { // Process manager
311360
let max_processes = self.cached_processes.len();
312361
if max_processes > 0 {
313362
self.selected_process = max_processes.saturating_sub(1);
314363
self.process_list_state.select(Some(self.selected_process));
315364
}
316365
}
317-
3 => { // File browser
366+
Panel::FileExplorer => { // File browser
318367
let max_files = self.dir_entries.len();
319368
if max_files > 0 {
320369
self.selected_file = max_files - 1;
@@ -325,15 +374,17 @@ impl App {
325374
}
326375
}
327376
KeyCode::Enter => {
328-
if self.selected_panel == 3 {
377+
if self.selected_panel == Panel::FileExplorer {
329378
self.navigate_into_selected();
330379
}
331380
}
332381
KeyCode::Char('r') => {
333382
// Force refresh with rate limiting
334383
if self.last_manual_refresh.elapsed() >= MANUAL_REFRESH_COOLDOWN {
335384
self.system.refresh_all();
336-
self.dir_entries = Self::read_directory(&self.current_dir);
385+
let (dir_entries, dir_entry_paths) = Self::read_directory(&self.current_dir);
386+
self.dir_entries = dir_entries;
387+
self.dir_entry_paths = dir_entry_paths;
337388
self.last_manual_refresh = Instant::now();
338389
}
339390
}
@@ -342,10 +393,12 @@ impl App {
342393
}
343394
KeyCode::Backspace => {
344395
// Go up one directory (same as selecting "..")
345-
if self.selected_panel == 3 { // File browser panel
396+
if self.selected_panel == Panel::FileExplorer { // File browser panel
346397
if let Some(parent) = self.current_dir.parent() {
347398
self.current_dir = parent.to_path_buf();
348-
self.dir_entries = Self::read_directory(&self.current_dir);
399+
let (dir_entries, dir_entry_paths) = Self::read_directory(&self.current_dir);
400+
self.dir_entries = dir_entries;
401+
self.dir_entry_paths = dir_entry_paths;
349402
self.selected_file = 0;
350403
self.file_list_state.select(Some(0));
351404
}
@@ -356,27 +409,28 @@ impl App {
356409
}
357410

358411
fn navigate_into_selected(&mut self) {
359-
if self.selected_file >= self.dir_entries.len() {
412+
if self.selected_file >= self.dir_entries.len() || self.selected_file >= self.dir_entry_paths.len() {
360413
return;
361414
}
362415

363416
let selected_item = &self.dir_entries[self.selected_file];
417+
let selected_path = &self.dir_entry_paths[self.selected_file];
364418

365419
if selected_item == ".." {
366-
// Go up one directory
367-
if let Some(parent) = self.current_dir.parent() {
368-
self.current_dir = parent.to_path_buf();
369-
self.dir_entries = Self::read_directory(&self.current_dir);
370-
self.selected_file = 0;
371-
self.file_list_state.select(Some(0));
372-
}
420+
// Go up one directory using the stored parent path
421+
self.current_dir = selected_path.clone();
422+
let (dir_entries, dir_entry_paths) = Self::read_directory(&self.current_dir);
423+
self.dir_entries = dir_entries;
424+
self.dir_entry_paths = dir_entry_paths;
425+
self.selected_file = 0;
426+
self.file_list_state.select(Some(0));
373427
} else if selected_item.starts_with("📁") {
374-
// Enter directory - handle truncated names properly
375-
let dir_name = selected_item.trim_start_matches("📁 ").trim_end_matches("...");
376-
let new_path = self.current_dir.join(dir_name);
377-
if new_path.is_dir() {
378-
self.current_dir = new_path;
379-
self.dir_entries = Self::read_directory(&self.current_dir);
428+
// Enter directory using the stored original path
429+
if selected_path.is_dir() {
430+
self.current_dir = selected_path.clone();
431+
let (dir_entries, dir_entry_paths) = Self::read_directory(&self.current_dir);
432+
self.dir_entries = dir_entries;
433+
self.dir_entry_paths = dir_entry_paths;
380434
self.selected_file = 0;
381435
self.file_list_state.select(Some(0));
382436
}
@@ -385,15 +439,19 @@ impl App {
385439
}
386440

387441
fn select_next_panel(&mut self) {
388-
self.selected_panel = (self.selected_panel + 1) % 5; // 5 panels total
442+
let current_index = self.selected_panel.as_index();
443+
let next_index = (current_index + 1) % Panel::COUNT;
444+
self.selected_panel = Panel::from_index(next_index).unwrap_or(Panel::SystemMonitor);
389445
}
390446

391447
fn select_previous_panel(&mut self) {
392-
self.selected_panel = if self.selected_panel == 0 {
393-
4 // 5 panels total (0-4)
448+
let current_index = self.selected_panel.as_index();
449+
let prev_index = if current_index == 0 {
450+
Panel::COUNT - 1
394451
} else {
395-
self.selected_panel - 1
452+
current_index - 1
396453
};
454+
self.selected_panel = Panel::from_index(prev_index).unwrap_or(Panel::SystemMonitor);
397455
}
398456

399457
pub fn render_header(&self, frame: &mut Frame, area: Rect) {

src/main.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,11 +98,11 @@ fn render(app: &App, frame: &mut Frame) {
9898
.split(content_layout[1]);
9999

100100
// Render panels
101-
render_system_info(app, frame, left_layout[0], app.selected_panel == 0);
102-
render_clock(app, frame, right_layout[0], app.selected_panel == 1);
103-
render_tasks(app, frame, left_layout[1], app.selected_panel == 2);
104-
render_file_browser(app, frame, right_layout[1], app.selected_panel == 3);
105-
render_network_graph(app, frame, main_content_layout[1], app.selected_panel == 4);
101+
render_system_info(app, frame, left_layout[0], app.selected_panel == app::Panel::SystemMonitor);
102+
render_clock(app, frame, right_layout[0], app.selected_panel == app::Panel::SystemStatus);
103+
render_tasks(app, frame, left_layout[1], app.selected_panel == app::Panel::ProcessManager);
104+
render_file_browser(app, frame, right_layout[1], app.selected_panel == app::Panel::FileExplorer);
105+
render_network_graph(app, frame, main_content_layout[1], app.selected_panel == app::Panel::NetworkGraph);
106106
}
107107

108108
// Footer

0 commit comments

Comments
 (0)