From 4871f01655d1719e3385e08f2e3bcbe664885238 Mon Sep 17 00:00:00 2001 From: 4fu Date: Wed, 25 Mar 2026 18:10:32 +0800 Subject: [PATCH] fix(ls): fix empty output on non-English locales, hidden file leaking, and egg-info glob MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Set LC_ALL=C on the ls subprocess so date tokens are always "Mon DD HH:MM" (3 parts, 9 columns total). Non-English locales like Chinese output "3月25日 17:58" (2 parts, 8 columns), causing the filename parser to skip every entry and return "(empty)". - Filter out dotfile entries when show_all=false. Previously rtk ls always ran ls -la internally but never suppressed hidden files, so .dotnet/, .local/ etc. appeared without -a. - Support suffix-glob patterns in NOISE_DIRS (e.g. "*.egg-info"). The existing == comparison never matched glob-style entries; patterns starting with "*" now use ends_with instead. Add tests for all three cases. Co-authored-by: Zhiyuan Zheng --- src/ls.rs | 46 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/src/ls.rs b/src/ls.rs index d121123a..cc6ae9c6 100644 --- a/src/ls.rs +++ b/src/ls.rs @@ -52,6 +52,10 @@ pub fn run(args: &[String], verbose: u8) -> Result<()> { // Build ls -la + any extra flags the user passed (e.g. -R) // Strip -l, -a, -h (we handle all of these ourselves) let mut cmd = resolved_command("ls"); + // Force C locale so date format is always "Mon DD HH:MM" (3 tokens, 9 columns total). + // Without this, non-English locales (e.g. Chinese "3月25日 17:58") produce 8 columns + // and the filename parser silently drops every entry → "(empty)". + cmd.env("LC_ALL", "C"); cmd.arg("-la"); for flag in &flags { if flag.starts_with("--") { @@ -160,8 +164,22 @@ fn compact_ls(raw: &str, show_all: bool) -> String { continue; } + // Filter hidden entries unless -a + if !show_all && name.starts_with('.') { + continue; + } + // Filter noise dirs unless -a - if !show_all && NOISE_DIRS.iter().any(|noise| name == *noise) { + // Patterns starting with "*" are suffix globs (e.g. "*.egg-info") + if !show_all + && NOISE_DIRS.iter().any(|noise| { + if let Some(suffix) = noise.strip_prefix('*') { + name.ends_with(suffix) + } else { + name == *noise + } + }) + { continue; } @@ -266,13 +284,39 @@ mod tests { assert!(output.contains("main.rs")); } + #[test] + fn test_compact_filters_glob_noise() { + let input = "total 8\n\ + drwxr-xr-x 2 user staff 64 Jan 1 12:00 mypackage.egg-info\n\ + drwxr-xr-x 2 user staff 64 Jan 1 12:00 src\n"; + let output = compact_ls(input, false); + assert!(!output.contains("egg-info"), "*.egg-info glob should be filtered"); + assert!(output.contains("src/")); + } + + #[test] + fn test_compact_hides_dotfiles_by_default() { + let input = "total 8\n\ + drwxr-xr-x 2 user staff 64 Jan 1 12:00 .local\n\ + drwxr-xr-x 2 user staff 64 Jan 1 12:00 .dotnet\n\ + drwxr-xr-x 2 user staff 64 Jan 1 12:00 src\n\ + -rw-r--r-- 1 user staff 57 Jan 1 12:00 .bash_profile\n"; + let output = compact_ls(input, false); + assert!(!output.contains(".local"), "hidden dir should be hidden without -a"); + assert!(!output.contains(".dotnet"), "hidden dir should be hidden without -a"); + assert!(!output.contains(".bash_profile"), "hidden file should be hidden without -a"); + assert!(output.contains("src/")); + } + #[test] fn test_compact_show_all() { let input = "total 8\n\ drwxr-xr-x 2 user staff 64 Jan 1 12:00 .git\n\ + drwxr-xr-x 2 user staff 64 Jan 1 12:00 .local\n\ drwxr-xr-x 2 user staff 64 Jan 1 12:00 src\n"; let output = compact_ls(input, true); assert!(output.contains(".git/")); + assert!(output.contains(".local/")); assert!(output.contains("src/")); }