Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 42 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ fn main() -> Result<()> {
r#"function try
set -l script_path "{}"
set -l cmd (/usr/bin/env "{}" cd{} $argv 2>/dev/tty | string collect)
test $status -eq 0 && eval $cmd || echo $cmd
set -l cmd_status $status
test $cmd_status -eq 0 && eval $cmd || echo $cmd
end"#,
script_path.display(),
script_path.display(),
Expand All @@ -125,8 +126,16 @@ end"#,
return;;
esac;;
esac
cmd=$(/usr/bin/env "{}" cd{} "$@" 2>/dev/tty);
[ $? -eq 0 ] && eval "$cmd" || echo "$cmd";
tmp=$(mktemp 2>/dev/null || echo "/tmp/try-cmd-$$")
/usr/bin/env "{}" cd{} "$@" > "$tmp" 2>/dev/tty
cmd_status=$?
cmd=$(cat "$tmp" 2>/dev/null)
rm -f "$tmp" 2>/dev/null
if [ $cmd_status -eq 0 ] && [ -n "$cmd" ]; then
eval "$cmd"
else
[ -n "$cmd" ] && echo "$cmd"
fi
}}"#,
script_path.display(),
script_path.display(),
Expand Down Expand Up @@ -474,4 +483,34 @@ mod tests {
// join_shell trivial
assert_eq!(crate::util::join_shell(&["a".into(), "b".into()]), "a && b");
}

#[test]
fn test_init_script_uses_cmd_status_not_status() {
// Regression test: ensure init script uses cmd_status instead of status
// (status is read-only in zsh)
use std::process::Command;
let output = Command::new("cargo")
.args(["run", "--quiet", "--", "init"])
.output()
.expect("Failed to run try init");
let stdout = String::from_utf8(output.stdout).expect("Invalid UTF-8");
// POSIX shell wrapper should use cmd_status, not status
assert!(
stdout.contains("cmd_status=$?"),
"Init script should use cmd_status=$?, got: {stdout}"
);
assert!(
stdout.contains("$cmd_status"),
"Init script should reference $cmd_status, got: {stdout}"
);
// Should NOT contain the problematic standalone status= assignment
// Check for the pattern with word boundaries to avoid false matches
let has_standalone_status = stdout
.lines()
.any(|line| line.trim().starts_with("status=$?") || line.contains(" status=$?"));
assert!(
!has_standalone_status,
"Init script should not use status=$? (read-only in zsh), got: {stdout}"
);
}
Comment thread
cursor[bot] marked this conversation as resolved.
Resolved
}
1 change: 1 addition & 0 deletions src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ pub struct TryDir {
pub ctime: Option<SystemTime>,
pub mtime: Option<SystemTime>,
pub score: f64,
pub size: u64,
}
20 changes: 3 additions & 17 deletions src/selector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,12 +247,14 @@ impl TrySelector {
}
let ctime = meta.created().ok();
let mtime = meta.modified().ok();
let size = crate::util::calculate_dir_size(&path);
Comment thread
cursor[bot] marked this conversation as resolved.
Resolved
out.push(TryDir {
basename,
path,
ctime,
mtime,
score: 0.0,
size,
});
}
}
Expand Down Expand Up @@ -363,7 +365,7 @@ impl TrySelector {
t.basename,
t.path.display(),
files,
format_human_size(bytes)
crate::util::format_human_size(bytes)
)?;
tui::styled(
err,
Expand Down Expand Up @@ -400,19 +402,3 @@ impl TrySelector {
}
}
}

fn format_human_size(bytes: u64) -> String {
const UNITS: &[&str] = &["B", "K", "M", "G", "T"];
const BYTES_PER_KIB: f64 = 1_024.0;
let mut val = bytes as f64;
let mut idx = 0;
while val >= BYTES_PER_KIB && idx + 1 < UNITS.len() {
val /= BYTES_PER_KIB;
idx += 1;
}
if idx == 0 {
format!("{bytes}B")
} else {
format!("{:.1}{}", val, UNITS[idx])
}
}
6 changes: 3 additions & 3 deletions src/tui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,10 +255,10 @@ pub(crate) fn render(err: &mut io::Stderr, ctx: &RenderCtx<'_>) -> Result<()> {
}
write_highlighted(err, &t.basename, ctx.input_buf, is_sel)?;

// Right-side meta: mtime then score
// Right-side meta: size and mtime
let size_text = crate::util::format_human_size(t.size);
let time_text = format_relative_time(t.mtime);
let score_text = format!("{:.1}", t.score);
let meta = format!("{time_text}, {score_text}");
let meta = format!("{size_text}, {time_text}");

// Compute remaining columns; ensure we never overflow terminal width
let name_w = display_width(&t.basename);
Expand Down
37 changes: 37 additions & 0 deletions src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,3 +231,40 @@ pub(crate) fn dir_assign_for_shell(dir: &Path) -> String {
format!("dir={escaped}")
}
}

/// Format a byte size as a human-readable string (e.g., "1.5K", "23.4M").
pub(crate) fn format_human_size(bytes: u64) -> String {
const UNITS: &[&str] = &["B", "K", "M", "G", "T"];
const BYTES_PER_KIB: f64 = 1_024.0;
let mut val = bytes as f64;
let mut idx = 0;
while val >= BYTES_PER_KIB && idx + 1 < UNITS.len() {
val /= BYTES_PER_KIB;
idx += 1;
}
if idx == 0 {
format!("{bytes}B")
} else {
format!("{:.1}{}", val, UNITS[idx])
}
}

/// Calculate the total size of a directory recursively.
pub(crate) fn calculate_dir_size(path: &Path) -> u64 {
let mut total = 0u64;
fn walk(p: &Path, total: &mut u64) {
if let Ok(md) = std::fs::symlink_metadata(p) {
if md.is_file() {
*total += md.len();
} else if md.is_dir()
&& let Ok(rd) = std::fs::read_dir(p)
{
for e in rd.flatten() {
walk(&e.path(), total);
}
}
}
}
walk(path, &mut total);
total
}