Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,10 @@ libc = "0.2.*"
human-sort = "0.2.2"
# should stick to 0.1, the 0.2 needs some adaptation
# check https://github.com/lsd-rs/lsd/issues/1014
term_grid = "0.1"
term_grid = "0.2.0"
terminal_size = "0.4.*"
thiserror = "2.0"
sys-locale = "0.3"
#once_cell = "1.17.1"
once_cell = "1.21"
chrono = { version = "0.4", features = ["unstable-locales"] }
chrono-humanize = "0.2"
Expand Down
125 changes: 105 additions & 20 deletions src/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::icon::Icons;
use crate::meta::name::DisplayOption;
use crate::meta::{FileType, Meta, OwnerCache};
use std::collections::HashMap;
use term_grid::{Cell, Direction, Filling, Grid, GridOptions};
use term_grid::{Alignment, Cell, Direction, Filling, Grid, GridOptions};
use terminal_size::terminal_size;
use unicode_width::UnicodeWidthStr;

Expand Down Expand Up @@ -137,6 +137,7 @@ fn inner_display_grid(
cells.push(Cell {
width: get_visible_width(&block, flags.hyperlink == HyperlinkOption::Always),
contents: block,
alignment: Alignment::Left,
});
}
}
Expand Down Expand Up @@ -197,33 +198,31 @@ fn inner_display_grid(
output
}

fn add_header(flags: &Flags, cells: &[Cell], grid: &mut Grid) {
let num_columns: usize = flags.blocks.0.len();
fn add_header(flags: &Flags, _cells: &[Cell], grid: &mut Grid) {
for block in flags.blocks.0.iter() {
let header_text = block.get_header();
let header_width =
get_visible_width(header_text, flags.hyperlink == HyperlinkOption::Always);

let mut widths = flags
.blocks
.0
.iter()
.map(|b| get_visible_width(b.get_header(), flags.hyperlink == HyperlinkOption::Always))
.collect::<Vec<usize>>();

// find max widths of each column
for (index, cell) in cells.iter().enumerate() {
let index = index % num_columns;
widths[index] = std::cmp::max(widths[index], cell.width);
}

for (idx, block) in flags.blocks.0.iter().enumerate() {
// center and underline header
// Underline ONLY the header text (no padding)
let underlined_header = crossterm::style::Stylize::attribute(
format!("{: ^1$}", block.get_header(), widths[idx]),
header_text.to_string(),
crossterm::style::Attribute::Underlined,
)
.to_string();

// Use term_grid's alignment to handle padding - this keeps underline only on text
// Right-align numeric columns (Size, Date, INode, Links), left-align text columns
let alignment = if block.is_numeric() {
Alignment::Right
} else {
Alignment::Left
};

grid.add(Cell {
width: widths[idx],
width: header_width,
contents: underlined_header,
alignment,
});
}
}
Expand Down Expand Up @@ -269,6 +268,7 @@ fn inner_display_tree(
cells.push(Cell {
width: get_visible_width(&block, flags.hyperlink == HyperlinkOption::Always),
contents: block,
alignment: Alignment::Left,
});
}

Expand Down Expand Up @@ -987,4 +987,89 @@ mod tests {
drop(file);
drop(link);
}

/// Test for issue #1014: Grid layout should work correctly with colors
/// When files can fit on multiple columns, they should not be displayed vertically.
/// The fix is to use get_visible_width() for the Cell width field, which strips
/// ANSI escape codes from the width calculation.
#[test]
fn test_grid_layout_with_colors_issue_1014() {
// Force color output to ensure ANSI codes are present
crossterm::style::force_color_output(true);

let argv = ["lsd"]; // Default grid layout
let cli = Cli::try_parse_from(argv).unwrap();
let flags = Flags::configure_from(&cli, &Config::with_none()).unwrap();

// Create directory with multiple short-named files
let dir = assert_fs::TempDir::new().unwrap();
for i in 1..=10 {
dir.child(format!("f{}.txt", i)).touch().unwrap();
}

let metas = Meta::from_path(Path::new(dir.path()), false, PermissionFlag::Rwx)
.unwrap()
.recurse_into(1, &flags, None)
.unwrap()
.0
.unwrap();

// Use colors to ensure ANSI codes are in the output
let colors = Colors::new(color::ThemeOption::NoLscolors);
let icons = Icons::new(true, IconOption::Never, FlagTheme::Fancy, " ".to_string());
let git_theme = GitTheme::new();

// Build cells the same way inner_display_grid does
let padding_rules = get_padding_rules(&metas, &flags);
let mut test_cells = Vec::new();
let owner_cache = OwnerCache::default();

for meta in &metas {
let blocks = get_output(
meta,
&owner_cache,
&colors,
&icons,
&git_theme,
&flags,
&DisplayOption::FileName,
&padding_rules,
(0, ""),
);
for block in blocks {
test_cells.push(Cell {
width: get_visible_width(&block, false),
contents: block,
alignment: Alignment::Left,
});
}
}

// Create grid with these cells
let mut grid = Grid::new(GridOptions {
filling: Filling::Spaces(2),
direction: Direction::TopToBottom,
});
for cell in &test_cells {
grid.add(cell.clone());
}

// With 200 columns and ~7 char filenames, fit_into_width should succeed
let result = grid.fit_into_width(200);
assert!(
result.is_some(),
"fit_into_width returned None - grid layout failed with ANSI colors"
);

// Verify multiple files fit per line
let output = result.unwrap().to_string();
let newline_count = output.matches('\n').count();
assert!(
newline_count < 10,
"Grid layout broken: {} newlines for 10 files (should fit multiple per line)",
newline_count
);

dir.close().unwrap();
}
}
7 changes: 7 additions & 0 deletions src/flags/blocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,13 @@ impl Block {
Block::GitStatus => "Git",
}
}

pub fn is_numeric(&self) -> bool {
matches!(
self,
Block::INode | Block::Links | Block::Size | Block::SizeValue | Block::Date
)
}
}

impl TryFrom<&str> for Block {
Expand Down