Skip to content

Commit 0120059

Browse files
committed
finally fix git status test bug
1 parent 2cda458 commit 0120059

File tree

3 files changed

+121
-102
lines changed

3 files changed

+121
-102
lines changed

src/tui.rs

Lines changed: 50 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ use crate::git::{self, StatusCache};
88
use crate::icons;
99
use crate::utils;
1010
use crossterm::{
11-
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
11+
event::{
12+
self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEvent, KeyModifiers,
13+
},
1214
execute,
1315
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
1416
};
@@ -22,21 +24,18 @@ use ratatui::{
2224
};
2325
use std::env;
2426
use std::fs;
25-
use std::io::{self, Stdout, Write};
27+
use std::io::{stderr, stdout, IsTerminal, Write};
2628
use std::path::{Path, PathBuf};
2729
use std::process::Command;
2830

2931
// Platform-specific import for unix permissions
3032
#[cfg(unix)]
3133
use std::os::unix::fs::PermissionsExt;
3234

33-
// ... (rest of TUI file is unchanged, no new bugs were present here)
34-
// The existing TUI code should work correctly with the updated git.rs
35-
36-
// ... (pasting the rest of the file for completeness)
3735
enum PostExitAction {
3836
None,
3937
OpenFile(PathBuf),
38+
PrintPath(PathBuf),
4039
}
4140

4241
#[derive(Debug, Clone)]
@@ -152,30 +151,33 @@ impl AppState {
152151
}
153152

154153
pub fn run(args: &InteractiveArgs) -> anyhow::Result<()> {
155-
let root_path = match fs::canonicalize(&args.path) {
156-
Ok(path) => path,
157-
Err(e) => anyhow::bail!("Invalid path '{}': {}", args.path.display(), e),
158-
};
159-
160-
if !root_path.is_dir() {
154+
if !args.path.is_dir() {
161155
anyhow::bail!("'{}' is not a directory.", args.path.display());
162156
}
157+
let root_path = fs::canonicalize(&args.path)?;
163158

164159
let mut app_state = AppState::new(args, &root_path)?;
165160
let mut terminal = setup_terminal()?;
166161
let post_exit_action = run_app(&mut terminal, &mut app_state, args)?;
167162
restore_terminal(&mut terminal)?;
168163

169-
if let PostExitAction::OpenFile(path) = post_exit_action {
170-
let editor = env::var("EDITOR").unwrap_or_else(|_| {
171-
if cfg!(windows) {
172-
"notepad".to_string()
173-
} else {
174-
"vim".to_string()
175-
}
176-
});
177-
Command::new(editor).arg(path).status()?;
164+
match post_exit_action {
165+
PostExitAction::OpenFile(path) => {
166+
let editor = env::var("EDITOR").unwrap_or_else(|_| {
167+
if cfg!(windows) {
168+
"notepad".to_string()
169+
} else {
170+
"vim".to_string()
171+
}
172+
});
173+
Command::new(editor).arg(path).status()?;
174+
}
175+
PostExitAction::PrintPath(path) => {
176+
println!("{}", path.display());
177+
}
178+
PostExitAction::None => {}
178179
}
180+
179181
Ok(())
180182
}
181183

@@ -186,12 +188,23 @@ fn run_app<B: Backend + Write>(
186188
) -> anyhow::Result<PostExitAction> {
187189
loop {
188190
terminal.draw(|f| ui(f, app_state, args))?;
191+
189192
if let Event::Key(key) = event::read()? {
190-
match key.code {
191-
KeyCode::Char('q') | KeyCode::Esc => break Ok(PostExitAction::None),
192-
KeyCode::Down | KeyCode::Char('j') => app_state.next(),
193-
KeyCode::Up | KeyCode::Char('k') => app_state.previous(),
194-
KeyCode::Enter => {
193+
match key {
194+
KeyEvent { code: KeyCode::Char('s'), modifiers: KeyModifiers::CONTROL, .. } => {
195+
if let Some(entry) = app_state.get_selected_entry() {
196+
break Ok(PostExitAction::PrintPath(entry.path.clone()));
197+
}
198+
}
199+
KeyEvent { code: KeyCode::Char('q'), .. } | KeyEvent { code: KeyCode::Esc, .. } => {
200+
break Ok(PostExitAction::None);
201+
}
202+
KeyEvent { code: KeyCode::Down, .. }
203+
| KeyEvent { code: KeyCode::Char('j'), .. } => app_state.next(),
204+
KeyEvent { code: KeyCode::Up, .. } | KeyEvent { code: KeyCode::Char('k'), .. } => {
205+
app_state.previous()
206+
}
207+
KeyEvent { code: KeyCode::Enter, .. } => {
195208
if let Some(entry) = app_state.get_selected_entry() {
196209
if entry.is_dir {
197210
app_state.toggle_selected_directory();
@@ -291,7 +304,6 @@ fn scan_directory(
291304
let mut entries = Vec::new();
292305
let mut builder = WalkBuilder::new(path);
293306
builder.hidden(!args.all).git_ignore(args.gitignore);
294-
295307
for result in builder.build().flatten() {
296308
if result.path() == path {
297309
continue;
@@ -355,11 +367,17 @@ fn map_color(c: colored::Color) -> Color {
355367
}
356368
}
357369

358-
fn setup_terminal() -> anyhow::Result<Terminal<CrosstermBackend<Stdout>>> {
359-
let mut stdout = io::stdout();
370+
// --- Terminal setup and restore functions ---
371+
type TerminalWriter = CrosstermBackend<Box<dyn Write + Send>>;
372+
373+
fn setup_terminal() -> anyhow::Result<Terminal<TerminalWriter>> {
374+
// If stdout is a pipe, draw the TUI to stderr, otherwise use stdout.
375+
let writer: Box<dyn Write + Send> =
376+
if stdout().is_terminal() { Box::new(stdout()) } else { Box::new(stderr()) };
360377
enable_raw_mode()?;
361-
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
362-
let backend = CrosstermBackend::new(stdout);
378+
let mut writer_mut = writer;
379+
execute!(writer_mut, EnterAlternateScreen, EnableMouseCapture)?;
380+
let backend = CrosstermBackend::new(writer_mut);
363381
Terminal::new(backend).map_err(anyhow::Error::from)
364382
}
365383

@@ -370,6 +388,7 @@ fn restore_terminal<B: Backend + Write>(terminal: &mut Terminal<B>) -> anyhow::R
370388
Ok(())
371389
}
372390

391+
// ... (Unit tests remain the same)
373392
#[cfg(test)]
374393
mod tests {
375394
use super::*;

src/view.rs

Lines changed: 38 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::git;
55
use crate::icons;
66
use crate::utils;
77
use colored::{control, Colorize};
8-
use ignore::{WalkBuilder, WalkState};
8+
use ignore::{self, WalkBuilder, WalkState};
99
use std::fs;
1010
use std::io::{self, Write};
1111
use std::sync::atomic::{AtomicU32, Ordering};
@@ -17,28 +17,29 @@ use std::os::unix::fs::PermissionsExt;
1717

1818
/// Executes the classic directory tree view.
1919
pub fn run(args: &ViewArgs) -> anyhow::Result<()> {
20-
// Check if the path is a directory BEFORE trying to canonicalize it.
2120
if !args.path.is_dir() {
2221
anyhow::bail!("'{}' is not a directory.", args.path.display());
2322
}
24-
25-
// Now that we know the path is valid, canonicalize it to resolve symlinks.
26-
let root_path = Arc::new(fs::canonicalize(&args.path)?);
23+
let canonical_root = Arc::new(fs::canonicalize(&args.path)?);
2724

2825
match args.color {
2926
crate::app::ColorChoice::Always => control::set_override(true),
3027
crate::app::ColorChoice::Never => control::set_override(false),
3128
crate::app::ColorChoice::Auto => {}
3229
}
3330

34-
if writeln!(io::stdout(), "{}", root_path.display().to_string().blue().bold()).is_err() {
35-
return Ok(()); // Exit silently on broken pipe
31+
if writeln!(io::stdout(), "{}", args.path.display().to_string().blue().bold()).is_err() {
32+
return Ok(());
3633
}
3734

38-
let git_repo_status = if args.git_status { git::load_status(&root_path)? } else { None };
35+
let git_repo_status = if args.git_status { git::load_status(&canonical_root)? } else { None };
3936

40-
let mut builder = WalkBuilder::new(root_path.as_ref());
41-
builder.hidden(!args.all).git_ignore(args.gitignore).max_depth(args.level);
37+
// The WalkBuilder MUST be initialized with the original, non-canonicalized path.
38+
let mut builder = WalkBuilder::new(&args.path);
39+
builder.hidden(!args.all).git_ignore(args.gitignore);
40+
if let Some(level) = args.level {
41+
builder.max_depth(Some(level));
42+
}
4243

4344
let walker = builder.build_parallel();
4445
let dir_count = Arc::new(AtomicU32::new(0));
@@ -48,13 +49,12 @@ pub fn run(args: &ViewArgs) -> anyhow::Result<()> {
4849
let dir_count = Arc::clone(&dir_count);
4950
let file_count = Arc::clone(&file_count);
5051
let git_repo_status = git_repo_status.clone();
51-
let root_path = Arc::clone(&root_path);
5252

53+
// This is the factory closure. It moves the data into the worker closure.
5354
move || {
54-
let dir_count = Arc::clone(&dir_count);
55-
let file_count = Arc::clone(&file_count);
55+
let dir_count = dir_count.clone();
56+
let file_count = file_count.clone();
5657
let git_repo_status = git_repo_status.clone();
57-
let root_path = Arc::clone(&root_path);
5858

5959
Box::new(move |result| {
6060
let status_cache = git_repo_status.as_ref().map(|s| &s.cache);
@@ -68,7 +68,7 @@ pub fn run(args: &ViewArgs) -> anyhow::Result<()> {
6868
}
6969
};
7070

71-
if entry.path() == root_path.as_ref() {
71+
if entry.depth() == 0 {
7272
return WalkState::Continue;
7373
}
7474

@@ -78,25 +78,29 @@ pub fn run(args: &ViewArgs) -> anyhow::Result<()> {
7878
}
7979

8080
let git_status_str = if let (Some(cache), Some(root)) = (status_cache, repo_root) {
81-
if let Ok(relative_path) = entry.path().strip_prefix(root) {
82-
cache
83-
.get(relative_path)
84-
.map(|s| {
85-
let status_char = s.get_char();
86-
let color = match s {
87-
git::FileStatus::New | git::FileStatus::Renamed => {
88-
colored::Color::Green
89-
}
90-
git::FileStatus::Modified | git::FileStatus::Typechange => {
91-
colored::Color::Yellow
92-
}
93-
git::FileStatus::Deleted => colored::Color::Red,
94-
git::FileStatus::Conflicted => colored::Color::BrightRed,
95-
git::FileStatus::Untracked => colored::Color::Magenta,
96-
};
97-
format!("{} ", status_char).color(color).to_string()
98-
})
99-
.unwrap_or_else(|| " ".to_string())
81+
if let Ok(canonical_entry) = entry.path().canonicalize() {
82+
if let Ok(relative_path) = canonical_entry.strip_prefix(root) {
83+
cache
84+
.get(relative_path)
85+
.map(|s| {
86+
let status_char = s.get_char();
87+
let color = match s {
88+
git::FileStatus::New | git::FileStatus::Renamed => {
89+
colored::Color::Green
90+
}
91+
git::FileStatus::Modified | git::FileStatus::Typechange => {
92+
colored::Color::Yellow
93+
}
94+
git::FileStatus::Deleted => colored::Color::Red,
95+
git::FileStatus::Conflicted => colored::Color::BrightRed,
96+
git::FileStatus::Untracked => colored::Color::Magenta,
97+
};
98+
format!("{} ", status_char).color(color).to_string()
99+
})
100+
.unwrap_or_else(|| " ".to_string())
101+
} else {
102+
" ".to_string()
103+
}
100104
} else {
101105
" ".to_string()
102106
}
@@ -106,7 +110,6 @@ pub fn run(args: &ViewArgs) -> anyhow::Result<()> {
106110

107111
let metadata =
108112
if args.size || args.permissions { entry.metadata().ok() } else { None };
109-
110113
let permissions_str = if args.permissions {
111114
let mut perms_string = "----------".to_string();
112115
if let Some(md) = &metadata {
@@ -125,14 +128,12 @@ pub fn run(args: &ViewArgs) -> anyhow::Result<()> {
125128

126129
let indent = " ".repeat(entry.depth().saturating_sub(1));
127130
let name = entry.file_name().to_string_lossy();
128-
129131
let icon_str = if args.icons {
130132
let (icon, color) = icons::get_icon_for_path(entry.path(), is_dir);
131133
format!("{} ", icon.color(color))
132134
} else {
133135
String::new()
134136
};
135-
136137
let size_str = if args.size && !is_dir {
137138
metadata
138139
.as_ref()

0 commit comments

Comments
 (0)