Skip to content

Commit 538355f

Browse files
authored
ls: add -T support and fix --classify output (#7616)
* add -T option parsing * add usage of tab_size in display_grid * fix test_ls_columns with \t since this behavior is on by default * update test_tabsize_formatting * use grid DEFAULT_SEPARATOR_SIZE * update Tabs * fix test with column width * fix cspell * fix linter warning on match bool * add comment for 0 tab_size * update tabsize test with -C * update one of the tabs tests to use -x * remove comment and split tests for both x/C args
1 parent 8487e12 commit 538355f

File tree

2 files changed

+75
-26
lines changed

2 files changed

+75
-26
lines changed

src/uu/ls/src/ls.rs

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ use clap::{
3333
};
3434
use glob::{MatchOptions, Pattern};
3535
use lscolors::LsColors;
36-
use term_grid::{Direction, Filling, Grid, GridOptions};
36+
use term_grid::{DEFAULT_SEPARATOR_SIZE, Direction, Filling, Grid, GridOptions, SPACES_IN_TAB};
3737
use thiserror::Error;
3838
use uucore::error::USimpleError;
3939
use uucore::format::human::{SizeFormat, human_readable};
@@ -91,7 +91,7 @@ pub mod options {
9191
pub static LONG: &str = "long";
9292
pub static COLUMNS: &str = "C";
9393
pub static ACROSS: &str = "x";
94-
pub static TAB_SIZE: &str = "tabsize"; // silently ignored (see #3624)
94+
pub static TAB_SIZE: &str = "tabsize";
9595
pub static COMMAS: &str = "m";
9696
pub static LONG_NO_OWNER: &str = "g";
9797
pub static LONG_NO_GROUP: &str = "o";
@@ -385,6 +385,7 @@ pub struct Config {
385385
line_ending: LineEnding,
386386
dired: bool,
387387
hyperlink: bool,
388+
tab_size: usize,
388389
}
389390

390391
// Fields that can be removed or added to the long format
@@ -1086,6 +1087,16 @@ impl Config {
10861087
Dereference::DirArgs
10871088
};
10881089

1090+
let tab_size = if !needs_color {
1091+
options
1092+
.get_one::<String>(options::format::TAB_SIZE)
1093+
.and_then(|size| size.parse::<usize>().ok())
1094+
.or_else(|| std::env::var("TABSIZE").ok().and_then(|s| s.parse().ok()))
1095+
} else {
1096+
Some(0)
1097+
}
1098+
.unwrap_or(SPACES_IN_TAB);
1099+
10891100
Ok(Self {
10901101
format,
10911102
files,
@@ -1123,6 +1134,7 @@ impl Config {
11231134
line_ending: LineEnding::from_zero_flag(options.get_flag(options::ZERO)),
11241135
dired,
11251136
hyperlink,
1137+
tab_size,
11261138
})
11271139
}
11281140
}
@@ -1239,13 +1251,12 @@ pub fn uu_app() -> Command {
12391251
.action(ArgAction::SetTrue),
12401252
)
12411253
.arg(
1242-
// silently ignored (see #3624)
12431254
Arg::new(options::format::TAB_SIZE)
12441255
.short('T')
12451256
.long(options::format::TAB_SIZE)
12461257
.env("TABSIZE")
12471258
.value_name("COLS")
1248-
.help("Assume tab stops at each COLS instead of 8 (unimplemented)"),
1259+
.help("Assume tab stops at each COLS instead of 8"),
12491260
)
12501261
.arg(
12511262
Arg::new(options::format::COMMAS)
@@ -2554,10 +2565,24 @@ fn display_items(
25542565

25552566
match config.format {
25562567
Format::Columns => {
2557-
display_grid(names, config.width, Direction::TopToBottom, out, quoted)?;
2568+
display_grid(
2569+
names,
2570+
config.width,
2571+
Direction::TopToBottom,
2572+
out,
2573+
quoted,
2574+
config.tab_size,
2575+
)?;
25582576
}
25592577
Format::Across => {
2560-
display_grid(names, config.width, Direction::LeftToRight, out, quoted)?;
2578+
display_grid(
2579+
names,
2580+
config.width,
2581+
Direction::LeftToRight,
2582+
out,
2583+
quoted,
2584+
config.tab_size,
2585+
)?;
25612586
}
25622587
Format::Commas => {
25632588
let mut current_col = 0;
@@ -2625,6 +2650,7 @@ fn display_grid(
26252650
direction: Direction,
26262651
out: &mut BufWriter<Stdout>,
26272652
quoted: bool,
2653+
tab_size: usize,
26282654
) -> UResult<()> {
26292655
if width == 0 {
26302656
// If the width is 0 we print one single line
@@ -2674,14 +2700,13 @@ fn display_grid(
26742700
.map(|s| s.to_string_lossy().into_owned())
26752701
.collect();
26762702

2677-
// Determine whether to use tabs for separation based on whether any entry ends with '/'.
2678-
// If any entry ends with '/', it indicates that the -F flag is likely used to classify directories.
2679-
let use_tabs = names.iter().any(|name| name.ends_with('/'));
2680-
2681-
let filling = if use_tabs {
2682-
Filling::Text("\t".to_string())
2683-
} else {
2684-
Filling::Spaces(2)
2703+
// Since tab_size=0 means no \t, use Spaces separator for optimization.
2704+
let filling = match tab_size {
2705+
0 => Filling::Spaces(DEFAULT_SEPARATOR_SIZE),
2706+
_ => Filling::Tabs {
2707+
spaces: DEFAULT_SEPARATOR_SIZE,
2708+
tab_size,
2709+
},
26852710
};
26862711

26872712
let grid = Grid::new(

tests/by-util/test_ls.rs

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -837,7 +837,7 @@ fn test_ls_columns() {
837837

838838
for option in COLUMN_ARGS {
839839
let result = scene.ucmd().arg(option).succeeds();
840-
result.stdout_only("test-columns-1 test-columns-2 test-columns-3 test-columns-4\n");
840+
result.stdout_only("test-columns-1\ttest-columns-2\ttest-columns-3\ttest-columns-4\n");
841841
}
842842

843843
for option in COLUMN_ARGS {
@@ -846,7 +846,7 @@ fn test_ls_columns() {
846846
.arg("-w=40")
847847
.arg(option)
848848
.succeeds()
849-
.stdout_only("test-columns-1 test-columns-3\ntest-columns-2 test-columns-4\n");
849+
.stdout_only("test-columns-1\ttest-columns-3\ntest-columns-2\ttest-columns-4\n");
850850
}
851851

852852
// On windows we are always able to get the terminal size, so we can't simulate falling back to the
@@ -859,15 +859,15 @@ fn test_ls_columns() {
859859
.env("COLUMNS", "40")
860860
.arg(option)
861861
.succeeds()
862-
.stdout_only("test-columns-1 test-columns-3\ntest-columns-2 test-columns-4\n");
862+
.stdout_only("test-columns-1\ttest-columns-3\ntest-columns-2\ttest-columns-4\n");
863863
}
864864

865865
scene
866866
.ucmd()
867867
.env("COLUMNS", "garbage")
868868
.arg("-C")
869869
.succeeds()
870-
.stdout_is("test-columns-1 test-columns-2 test-columns-3 test-columns-4\n")
870+
.stdout_is("test-columns-1\ttest-columns-2\ttest-columns-3\ttest-columns-4\n")
871871
.stderr_is("ls: ignoring invalid width in environment variable COLUMNS: 'garbage'\n");
872872
}
873873
scene
@@ -4366,28 +4366,52 @@ fn test_tabsize_option() {
43664366
scene.ucmd().arg("-T").fails();
43674367
}
43684368

4369-
#[ignore = "issue #3624"]
43704369
#[test]
43714370
fn test_tabsize_formatting() {
4372-
let (at, mut ucmd) = at_and_ucmd!();
4371+
let scene = TestScenario::new(util_name!());
4372+
let at = &scene.fixtures;
43734373

43744374
at.touch("aaaaaaaa");
43754375
at.touch("bbbb");
43764376
at.touch("cccc");
43774377
at.touch("dddddddd");
43784378

4379-
ucmd.args(&["-T", "4"])
4379+
scene
4380+
.ucmd()
4381+
.args(&["-x", "-w18", "-T4"])
43804382
.succeeds()
4381-
.stdout_is("aaaaaaaa bbbb\ncccc\t dddddddd");
4383+
.stdout_is("aaaaaaaa bbbb\ncccc\t dddddddd\n");
43824384

4383-
ucmd.args(&["-T", "2"])
4385+
scene
4386+
.ucmd()
4387+
.args(&["-C", "-w18", "-T4"])
4388+
.succeeds()
4389+
.stdout_is("aaaaaaaa cccc\nbbbb\t dddddddd\n");
4390+
4391+
scene
4392+
.ucmd()
4393+
.args(&["-x", "-w18", "-T2"])
43844394
.succeeds()
4385-
.stdout_is("aaaaaaaa bbbb\ncccc\t\t dddddddd");
4395+
.stdout_is("aaaaaaaa\tbbbb\ncccc\t\t\tdddddddd\n");
4396+
4397+
scene
4398+
.ucmd()
4399+
.args(&["-C", "-w18", "-T2"])
4400+
.succeeds()
4401+
.stdout_is("aaaaaaaa\tcccc\nbbbb\t\t\tdddddddd\n");
4402+
4403+
scene
4404+
.ucmd()
4405+
.args(&["-x", "-w18", "-T0"])
4406+
.succeeds()
4407+
.stdout_is("aaaaaaaa bbbb\ncccc dddddddd\n");
43864408

43874409
// use spaces
4388-
ucmd.args(&["-T", "0"])
4410+
scene
4411+
.ucmd()
4412+
.args(&["-C", "-w18", "-T0"])
43894413
.succeeds()
4390-
.stdout_is("aaaaaaaa bbbb\ncccc dddddddd");
4414+
.stdout_is("aaaaaaaa cccc\nbbbb dddddddd\n");
43914415
}
43924416

43934417
#[cfg(any(

0 commit comments

Comments
 (0)