Skip to content

Commit 9e47e4d

Browse files
CopilotKSXGitHub
andcommitted
refactor: consolidate AnsiPrefixes at crate root; Coloring struct; ColoredTreeHorizontalSlice
Co-authored-by: KSXGitHub <11488886+KSXGitHub@users.noreply.github.com>
1 parent 3c22b24 commit 9e47e4d

10 files changed

Lines changed: 184 additions & 59 deletions

File tree

Cargo.lock

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,4 @@ maplit = "1.0.2"
8484
normalize-path = "0.2.1"
8585
pretty_assertions = "1.4.1"
8686
rand = "0.10.0"
87+
strip-ansi-escapes = "0.2.1"

src/ansi_prefixes.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
use lscolors::{Indicator, LsColors};
2+
3+
/// ANSI prefix strings for each color variant, read from `LS_COLORS`.
4+
#[derive(Debug, Clone)]
5+
pub struct AnsiPrefixes {
6+
pub(crate) directory: String,
7+
pub(crate) normal: String,
8+
pub(crate) executable: String,
9+
pub(crate) symlink: String,
10+
}
11+
12+
impl AnsiPrefixes {
13+
/// Initialize by reading the current environment's `LS_COLORS`.
14+
pub fn from_env() -> Self {
15+
let ls_colors = LsColors::from_env().unwrap_or_default();
16+
let prefix_for = |indicator: Indicator| {
17+
ls_colors
18+
.style_for_indicator(indicator)
19+
.map(|s| s.to_nu_ansi_term_style().prefix().to_string())
20+
.unwrap_or_default()
21+
};
22+
AnsiPrefixes {
23+
directory: prefix_for(Indicator::Directory),
24+
normal: prefix_for(Indicator::RegularFile),
25+
executable: prefix_for(Indicator::ExecutableFile),
26+
symlink: prefix_for(Indicator::SymbolicLink),
27+
}
28+
}
29+
}

src/app.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use crate::{
1212
runtime_error::RuntimeError,
1313
size,
1414
visualizer::{BarAlignment, ColumnWidthDistribution, Direction, Visualizer},
15+
AnsiPrefixes,
1516
};
1617
use clap::Parser;
1718
use hdd::any_path_is_in_hdd;
@@ -27,13 +28,16 @@ use crate::get_size::{GetBlockCount, GetBlockSize};
2728
pub struct App {
2829
/// The CLI arguments.
2930
args: Args,
31+
/// ANSI prefix strings read from `LS_COLORS`.
32+
ansi_prefixes: AnsiPrefixes,
3033
}
3134

3235
impl App {
3336
/// Initialize the application from the environment.
3437
pub fn from_env() -> Self {
3538
App {
3639
args: Args::parse(),
40+
ansi_prefixes: AnsiPrefixes::from_env(),
3741
}
3842
}
3943

@@ -46,6 +50,8 @@ impl App {
4650
//
4751
// The other operations which are invoked frequently should not utilize dynamic dispatch.
4852

53+
// Extract `ansi_prefixes` by move before `match self.args` consumes the rest of `self`.
54+
let ansi_prefixes = self.ansi_prefixes;
4955
let column_width_distribution = self.args.column_width_distribution();
5056

5157
if self.args.json_input {
@@ -310,6 +316,7 @@ impl App {
310316
min_ratio,
311317
no_sort,
312318
color,
319+
ansi_prefixes,
313320
}
314321
.run(),
315322
)*} };

src/app/sub.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ use crate::{
1010
runtime_error::RuntimeError,
1111
size,
1212
status_board::GLOBAL_STATUS_BOARD,
13-
visualizer::{BarAlignment, Color, ColumnWidthDistribution, Direction, Visualizer},
13+
visualizer::{BarAlignment, Color, Coloring, ColumnWidthDistribution, Direction, Visualizer},
14+
AnsiPrefixes,
1415
};
1516
use pipe_trait::Pipe;
1617
use serde::Serialize;
@@ -56,6 +57,8 @@ where
5657
pub no_sort: bool,
5758
/// When to use colors in the output.
5859
pub color: ColorWhen,
60+
/// ANSI prefix strings read from `LS_COLORS`.
61+
pub ansi_prefixes: AnsiPrefixes,
5962
}
6063

6164
impl<Size, SizeGetter, HardlinksHandler, Report> Sub<Size, SizeGetter, HardlinksHandler, Report>
@@ -82,6 +85,7 @@ where
8285
min_ratio,
8386
no_sort,
8487
color,
88+
ansi_prefixes,
8589
} = self;
8690

8791
let max_depth = max_depth.get();
@@ -106,6 +110,7 @@ where
106110
files: vec![".".into()],
107111
hardlinks_handler,
108112
reporter,
113+
ansi_prefixes,
109114
..self
110115
}
111116
.run();
@@ -201,10 +206,10 @@ where
201206
ColorWhen::Auto => stdout().is_terminal(),
202207
};
203208

204-
let coloring: Option<HashMap<OsStringDisplay, Color>> = if use_color {
209+
let coloring: Option<Coloring<OsStringDisplay>> = if use_color {
205210
let mut map = HashMap::new();
206211
build_coloring_map(&data_tree, PathBuf::new(), &mut map);
207-
Some(map)
212+
Some(Coloring::new(ansi_prefixes, map))
208213
} else {
209214
None
210215
};

src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ pub use clap_complete;
3636
#[cfg(feature = "cli")]
3737
pub use clap_utilities;
3838

39+
pub mod ansi_prefixes;
3940
pub mod bytes_format;
4041
pub mod data_tree;
4142
pub mod fs_tree_builder;
@@ -50,4 +51,6 @@ pub mod status_board;
5051
pub mod tree_builder;
5152
pub mod visualizer;
5253

54+
pub use ansi_prefixes::AnsiPrefixes;
55+
5356
pub use zero_copy_pads;

src/visualizer.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@ pub mod tree;
99

1010
pub use bar_alignment::BarAlignment;
1111
pub use child_position::ChildPosition;
12-
pub use coloring::Color;
12+
pub use coloring::{AnsiPrefix, Color, Coloring};
1313
pub use column_width_distribution::ColumnWidthDistribution;
1414
pub use direction::Direction;
1515
pub use parenthood::Parenthood;
1616
pub use proportion_bar::{ProportionBar, ProportionBarBlock};
1717
pub use tree::{TreeHorizontalSlice, TreeSkeletalComponent};
1818

1919
use super::{data_tree::DataTree, size};
20-
use std::{collections::HashMap, fmt::Display};
20+
use std::fmt::Display;
2121

2222
/// Visualize a [`DataTree`].
2323
///
@@ -62,7 +62,7 @@ where
6262
/// Distribution and total number of characters/blocks can be placed in a line.
6363
pub column_width_distribution: ColumnWidthDistribution,
6464
/// Mapping of names to colors for colorful output.
65-
pub coloring: Option<&'a HashMap<Name, Color>>,
65+
pub coloring: Option<&'a Coloring<Name>>,
6666
}
6767

6868
mod copy;

src/visualizer/coloring.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
use crate::AnsiPrefixes;
2+
use std::{collections::HashMap, fmt, hash::Hash};
3+
14
/// The coloring to apply to a node name.
25
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36
pub enum Color {
@@ -10,3 +13,51 @@ pub enum Color {
1013
/// Color as a symbolic link.
1114
Symlink,
1215
}
16+
17+
impl Color {
18+
/// Get the ANSI prefix for this color from the given prefix table.
19+
pub fn ansi_prefix(self, prefixes: &AnsiPrefixes) -> AnsiPrefix<'_> {
20+
AnsiPrefix(match self {
21+
Color::Directory => &prefixes.directory,
22+
Color::Normal => &prefixes.normal,
23+
Color::Executable => &prefixes.executable,
24+
Color::Symlink => &prefixes.symlink,
25+
})
26+
}
27+
}
28+
29+
/// ANSI prefix wrapper for a [`Color`] variant, implements [`fmt::Display`].
30+
pub struct AnsiPrefix<'a>(pub(crate) &'a str);
31+
32+
impl fmt::Display for AnsiPrefix<'_> {
33+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34+
f.write_str(self.0)
35+
}
36+
}
37+
38+
impl AnsiPrefix<'_> {
39+
/// Returns the reset suffix to emit after this prefix, or `""` if no prefix.
40+
pub fn suffix(&self) -> &'static str {
41+
if self.0.is_empty() {
42+
""
43+
} else {
44+
"\x1b[0m"
45+
}
46+
}
47+
}
48+
49+
/// Coloring configuration: ANSI prefix strings from the environment and a name-to-color map.
50+
#[derive(Debug)]
51+
pub struct Coloring<Name> {
52+
/// ANSI prefix strings read from `LS_COLORS`.
53+
pub(crate) ansi_prefixes: AnsiPrefixes,
54+
/// Map from node name to color.
55+
pub(crate) map: HashMap<Name, Color>,
56+
}
57+
58+
impl<Name: Hash + Eq> Coloring<Name> {
59+
/// Create a new [`Coloring`] from ANSI prefixes and a name-to-color map.
60+
pub fn new(ansi_prefixes: AnsiPrefixes, map: HashMap<Name, Color>) -> Self {
61+
Coloring { ansi_prefixes, map }
62+
}
63+
}

src/visualizer/methods.rs

Lines changed: 33 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -13,74 +13,41 @@ use table::*;
1313
use tree_table::*;
1414

1515
use super::{ChildPosition, Color, ColumnWidthDistribution, TreeHorizontalSlice, Visualizer};
16-
use crate::size;
17-
use lscolors::{Indicator, LsColors};
16+
use crate::{size, AnsiPrefixes};
1817
use std::{
1918
cmp::min,
2019
fmt::{self, Display},
2120
hash::Hash,
22-
sync::LazyLock,
2321
};
2422
use zero_copy_pads::{align_left, align_right, Width};
2523

26-
struct AnsiPrefixes {
27-
directory: String,
28-
normal: String,
29-
executable: String,
30-
symlink: String,
31-
}
32-
33-
static ANSI_PREFIXES: LazyLock<AnsiPrefixes> = LazyLock::new(|| {
34-
let ls_colors = LsColors::from_env().unwrap_or_default();
35-
let compute = |indicator: Indicator| {
36-
ls_colors
37-
.style_for_indicator(indicator)
38-
.map(|s| s.to_nu_ansi_term_style().prefix().to_string())
39-
.unwrap_or_default()
40-
};
41-
AnsiPrefixes {
42-
directory: compute(Indicator::Directory),
43-
normal: compute(Indicator::RegularFile),
44-
executable: compute(Indicator::ExecutableFile),
45-
symlink: compute(Indicator::SymbolicLink),
46-
}
47-
});
48-
49-
fn color_ansi_prefix(color: Color) -> &'static str {
50-
match color {
51-
Color::Directory => &ANSI_PREFIXES.directory,
52-
Color::Normal => &ANSI_PREFIXES.normal,
53-
Color::Executable => &ANSI_PREFIXES.executable,
54-
Color::Symlink => &ANSI_PREFIXES.symlink,
55-
}
56-
}
57-
58-
struct ColoredSlice<'a> {
59-
slice: &'a TreeHorizontalSlice<String>,
24+
struct ColoredTreeHorizontalSlice<'a> {
25+
slice: TreeHorizontalSlice<String>,
6026
color: Color,
27+
ansi_prefixes: &'a AnsiPrefixes,
6128
}
6229

63-
impl fmt::Display for ColoredSlice<'_> {
30+
impl fmt::Display for ColoredTreeHorizontalSlice<'_> {
6431
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
6532
let TreeHorizontalSlice {
6633
ancestor_relative_positions,
6734
skeletal_component,
6835
name,
69-
} = self.slice;
36+
} = &self.slice;
7037
for pos in ancestor_relative_positions {
7138
let connector = match pos {
7239
ChildPosition::Init => "│ ",
7340
ChildPosition::Last => " ",
7441
};
7542
write!(f, "{connector}")?;
7643
}
77-
let prefix = color_ansi_prefix(self.color);
78-
let suffix = if prefix.is_empty() { "" } else { "\x1b[0m" };
44+
let prefix = self.color.ansi_prefix(self.ansi_prefixes);
45+
let suffix = prefix.suffix();
7946
write!(f, "{skeletal_component}{prefix}{name}{suffix}")
8047
}
8148
}
8249

83-
impl Width for ColoredSlice<'_> {
50+
impl Width for ColoredTreeHorizontalSlice<'_> {
8451
fn width(&self) -> usize {
8552
self.slice.width()
8653
}
@@ -151,25 +118,38 @@ where
151118
bar_table
152119
.into_iter()
153120
.map(|row| {
154-
let BarRow { tree_row, proportion_bar } = row;
155-
let TreeRow { initial_row, tree_horizontal_slice: slice } = tree_row;
156-
157-
let node_color = self.coloring.and_then(|coloring| {
158-
if initial_row.node_info.children_count > 0 {
121+
let BarRow {
122+
tree_row,
123+
proportion_bar,
124+
} = row;
125+
let TreeRow {
126+
initial_row,
127+
tree_horizontal_slice,
128+
} = tree_row;
129+
130+
let colored = self.coloring.and_then(|coloring| {
131+
let color = if initial_row.node_info.children_count > 0 {
159132
Some(Color::Directory)
160133
} else {
161-
coloring.get(initial_row.node_info.name).copied()
162-
}
134+
coloring.map.get(initial_row.node_info.name).copied()
135+
}?;
136+
Some((color, &coloring.ansi_prefixes))
163137
});
164138

165139
let aligned_colored_slice;
166140
let aligned_plain_slice;
167-
let tree = if let Some(color) = node_color {
168-
aligned_colored_slice =
169-
align_left(ColoredSlice { slice: &slice, color }, tree_width);
141+
let tree = if let Some((color, ansi_prefixes)) = colored {
142+
aligned_colored_slice = align_left(
143+
ColoredTreeHorizontalSlice {
144+
slice: tree_horizontal_slice,
145+
color,
146+
ansi_prefixes,
147+
},
148+
tree_width,
149+
);
170150
format_args!("{aligned_colored_slice}")
171151
} else {
172-
aligned_plain_slice = align_left(&slice, tree_width);
152+
aligned_plain_slice = align_left(&tree_horizontal_slice, tree_width);
173153
format_args!("{aligned_plain_slice}")
174154
};
175155

0 commit comments

Comments
 (0)