Skip to content

Commit ed0707a

Browse files
authored
fix(builtins): preserve full path in ls output for file arguments (#858)
Closes #849
1 parent 0275002 commit ed0707a

2 files changed

Lines changed: 80 additions & 27 deletions

File tree

crates/bashkit/src/builtins/ls.rs

Lines changed: 38 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ impl Builtin for Ls {
7979
let mut output = String::new();
8080
let multiple_paths = paths.len() > 1 || opts.recursive;
8181

82+
// Separate file and directory arguments (like real ls)
83+
let mut file_args: Vec<(&str, crate::fs::Metadata)> = Vec::new();
84+
let mut dir_args: Vec<(usize, &str, std::path::PathBuf)> = Vec::new();
85+
8286
for (i, path_str) in paths.iter().enumerate() {
8387
let path = resolve_path(ctx.cwd, path_str);
8488

@@ -93,37 +97,44 @@ impl Builtin for Ls {
9397
));
9498
}
9599

96-
// Check if it's a file or directory
97100
let metadata = ctx.fs.stat(&path).await?;
98101

99102
if metadata.file_type.is_file() {
100-
// Single file - just list it
101-
let name = Path::new(path_str)
102-
.file_name()
103-
.map(|s| s.to_string_lossy().to_string())
104-
.unwrap_or_else(|| path_str.to_string());
105-
106-
if opts.long {
107-
output.push_str(&format_long_entry(&name, &metadata, opts.human));
108-
} else {
109-
output.push_str(&name);
110-
output.push('\n');
111-
}
103+
file_args.push((path_str, metadata));
112104
} else {
113-
// Directory
114-
if let Err(e) = list_directory(
115-
&ctx,
116-
&path,
117-
path_str,
118-
&mut output,
119-
&opts,
120-
multiple_paths,
121-
i > 0,
122-
)
123-
.await
124-
{
125-
return Ok(ExecResult::err(format!("ls: {}\n", e), 2));
126-
}
105+
dir_args.push((i, path_str, path));
106+
}
107+
}
108+
109+
// Sort file arguments by time if -t, preserving original paths
110+
if opts.sort_by_time {
111+
file_args.sort_by(|a, b| b.1.modified.cmp(&a.1.modified));
112+
}
113+
114+
// Output file arguments first (preserving path as given by user)
115+
for (path_str, metadata) in &file_args {
116+
if opts.long {
117+
output.push_str(&format_long_entry(path_str, metadata, opts.human));
118+
} else {
119+
output.push_str(path_str);
120+
output.push('\n');
121+
}
122+
}
123+
124+
// Then output directory listings
125+
for (i, path_str, path) in &dir_args {
126+
if let Err(e) = list_directory(
127+
&ctx,
128+
path,
129+
path_str,
130+
&mut output,
131+
&opts,
132+
multiple_paths,
133+
*i > 0 || !file_args.is_empty(),
134+
)
135+
.await
136+
{
137+
return Ok(ExecResult::err(format!("ls: {}\n", e), 2));
127138
}
128139
}
129140

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
### ls_file_preserves_path
2+
# ls with file arguments should preserve the full path in output
3+
mkdir -p /tmp/lsdir
4+
echo x > /tmp/lsdir/a.md
5+
echo y > /tmp/lsdir/b.md
6+
ls /tmp/lsdir/a.md /tmp/lsdir/b.md
7+
### expect
8+
/tmp/lsdir/a.md
9+
/tmp/lsdir/b.md
10+
### end
11+
12+
### ls_file_preserves_path_sorted_by_time
13+
# ls -t with file arguments should preserve the full path in output
14+
mkdir -p /tmp/lstdir
15+
echo x > /tmp/lstdir/a.md
16+
sleep 0.01
17+
echo y > /tmp/lstdir/b.md
18+
ls -t /tmp/lstdir/a.md /tmp/lstdir/b.md
19+
### expect
20+
/tmp/lstdir/b.md
21+
/tmp/lstdir/a.md
22+
### end
23+
24+
### ls_directory_shows_filenames_only
25+
# ls on a directory should show filenames only, not full paths
26+
mkdir -p /tmp/lsdironly
27+
echo x > /tmp/lsdironly/file1.txt
28+
echo y > /tmp/lsdironly/file2.txt
29+
ls /tmp/lsdironly
30+
### expect
31+
file1.txt
32+
file2.txt
33+
### end
34+
35+
### ls_single_file_preserves_path
36+
# ls with a single file argument should preserve the full path
37+
mkdir -p /tmp/lssingle
38+
echo x > /tmp/lssingle/test.txt
39+
ls /tmp/lssingle/test.txt
40+
### expect
41+
/tmp/lssingle/test.txt
42+
### end

0 commit comments

Comments
 (0)