Skip to content

Commit ab5cc0a

Browse files
CopilotKSXGitHub
andcommitted
refactor(r7): rename AnsiPrefixes→LsColors, private fields, restructure coloring, move color logic to App::run
Co-authored-by: KSXGitHub <11488886+KSXGitHub@users.noreply.github.com>
1 parent 9e47e4d commit ab5cc0a

7 files changed

Lines changed: 223 additions & 123 deletions

File tree

src/app.rs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,24 @@ pub mod sub;
33
pub use sub::Sub;
44

55
use crate::{
6-
args::{Args, Quantity, Threads},
6+
args::{Args, ColorWhen, Quantity, Threads},
77
bytes_format::BytesFormat,
88
get_size::{GetApparentSize, GetSize},
99
hardlink,
1010
json_data::{JsonData, JsonDataBody, JsonShared, JsonTree},
11+
ls_colors::LsColors,
1112
reporter::{ErrorOnlyReporter, ErrorReport, ProgressAndErrorReporter, ProgressReport},
1213
runtime_error::RuntimeError,
1314
size,
1415
visualizer::{BarAlignment, ColumnWidthDistribution, Direction, Visualizer},
15-
AnsiPrefixes,
1616
};
1717
use clap::Parser;
1818
use hdd::any_path_is_in_hdd;
1919
use pipe_trait::Pipe;
20-
use std::{io::stdin, time::Duration};
20+
use std::{
21+
io::{stdin, stdout, IsTerminal},
22+
time::Duration,
23+
};
2124
use sub::JsonOutputParam;
2225
use sysinfo::Disks;
2326

@@ -28,16 +31,13 @@ use crate::get_size::{GetBlockCount, GetBlockSize};
2831
pub struct App {
2932
/// The CLI arguments.
3033
args: Args,
31-
/// ANSI prefix strings read from `LS_COLORS`.
32-
ansi_prefixes: AnsiPrefixes,
3334
}
3435

3536
impl App {
3637
/// Initialize the application from the environment.
3738
pub fn from_env() -> Self {
3839
App {
3940
args: Args::parse(),
40-
ansi_prefixes: AnsiPrefixes::from_env(),
4141
}
4242
}
4343

@@ -50,8 +50,6 @@ impl App {
5050
//
5151
// The other operations which are invoked frequently should not utilize dynamic dispatch.
5252

53-
// Extract `ansi_prefixes` by move before `match self.args` consumes the rest of `self`.
54-
let ansi_prefixes = self.ansi_prefixes;
5553
let column_width_distribution = self.args.column_width_distribution();
5654

5755
if self.args.json_input {
@@ -315,8 +313,11 @@ impl App {
315313
max_depth,
316314
min_ratio,
317315
no_sort,
318-
color,
319-
ansi_prefixes,
316+
color: match color {
317+
ColorWhen::Always => Some(LsColors::from_env()),
318+
ColorWhen::Never => None,
319+
ColorWhen::Auto => stdout().is_terminal().then(LsColors::from_env),
320+
},
320321
}
321322
.run(),
322323
)*} };

src/app/sub.rs

Lines changed: 13 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
use crate::{
2-
args::{ColorWhen, Depth, Fraction},
2+
args::{Depth, Fraction},
33
data_tree::DataTree,
44
fs_tree_builder::FsTreeBuilder,
55
get_size::GetSize,
66
hardlink::{DeduplicateSharedSize, HardlinkIgnorant, RecordHardlinks},
77
json_data::{BinaryVersion, JsonData, JsonDataBody, JsonShared, JsonTree, SchemaVersion},
8+
ls_colors::LsColors,
89
os_string_display::OsStringDisplay,
910
reporter::ParallelReporter,
1011
runtime_error::RuntimeError,
1112
size,
1213
status_board::GLOBAL_STATUS_BOARD,
1314
visualizer::{BarAlignment, Color, Coloring, ColumnWidthDistribution, Direction, Visualizer},
14-
AnsiPrefixes,
1515
};
1616
use pipe_trait::Pipe;
1717
use serde::Serialize;
1818
use std::{
1919
collections::HashMap,
20-
io::{stdout, IsTerminal},
20+
io::stdout,
2121
iter::once,
2222
path::{Path, PathBuf},
2323
};
@@ -55,10 +55,8 @@ where
5555
pub min_ratio: Fraction,
5656
/// Preserve order of entries.
5757
pub no_sort: bool,
58-
/// When to use colors in the output.
59-
pub color: ColorWhen,
60-
/// ANSI prefix strings read from `LS_COLORS`.
61-
pub ansi_prefixes: AnsiPrefixes,
58+
/// Whether to color the output.
59+
pub color: Option<LsColors>,
6260
}
6361

6462
impl<Size, SizeGetter, HardlinksHandler, Report> Sub<Size, SizeGetter, HardlinksHandler, Report>
@@ -85,7 +83,6 @@ where
8583
min_ratio,
8684
no_sort,
8785
color,
88-
ansi_prefixes,
8986
} = self;
9087

9188
let max_depth = max_depth.get();
@@ -110,7 +107,7 @@ where
110107
files: vec![".".into()],
111108
hardlinks_handler,
112109
reporter,
113-
ansi_prefixes,
110+
color,
114111
..self
115112
}
116113
.run();
@@ -200,19 +197,11 @@ where
200197
.or(deduplication_result);
201198
}
202199

203-
let use_color = match color {
204-
ColorWhen::Always => true,
205-
ColorWhen::Never => false,
206-
ColorWhen::Auto => stdout().is_terminal(),
207-
};
208-
209-
let coloring: Option<Coloring<OsStringDisplay>> = if use_color {
200+
let coloring: Option<Coloring<OsStringDisplay>> = color.map(|ls_colors| {
210201
let mut map = HashMap::new();
211202
build_coloring_map(&data_tree, PathBuf::new(), &mut map);
212-
Some(Coloring::new(ansi_prefixes, map))
213-
} else {
214-
None
215-
};
203+
Coloring::new(ls_colors, map)
204+
});
216205

217206
let visualizer = Visualizer {
218207
data_tree: &data_tree,
@@ -302,13 +291,13 @@ fn build_coloring_map(
302291
map: &mut HashMap<OsStringDisplay, Color>,
303292
) {
304293
let node_path = path.join(node.name().as_os_str());
305-
if node.children().is_empty() {
306-
map.insert(node.name().clone(), file_color(&node_path));
307-
} else {
294+
if !node.children().is_empty() {
308295
for child in node.children() {
309296
build_coloring_map(child, node_path.clone(), map);
310297
}
298+
return;
311299
}
300+
map.insert(node.name().clone(), file_color(&node_path));
312301
}
313302

314303
fn file_color(path: &Path) -> Color {
@@ -327,7 +316,7 @@ fn file_color(path: &Path) -> Color {
327316
fn is_executable(path: &Path) -> bool {
328317
use std::os::unix::fs::PermissionsExt;
329318
path.metadata()
330-
.map(|m| m.permissions().mode() & 0o111 != 0)
319+
.map(|stats| stats.permissions().mode() & 0o111 != 0)
331320
.unwrap_or(false)
332321
}
333322

src/lib.rs

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

39-
pub mod ansi_prefixes;
4039
pub mod bytes_format;
4140
pub mod data_tree;
4241
pub mod fs_tree_builder;
4342
pub mod get_size;
4443
pub mod hardlink;
4544
pub mod inode;
4645
pub mod json_data;
46+
pub mod ls_colors;
4747
pub mod os_string_display;
4848
pub mod reporter;
4949
pub mod size;
5050
pub mod status_board;
5151
pub mod tree_builder;
5252
pub mod visualizer;
5353

54-
pub use ansi_prefixes::AnsiPrefixes;
55-
5654
pub use zero_copy_pads;

src/ls_colors.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
use crate::visualizer::coloring::Color;
2+
use lscolors::{Indicator, LsColors as LsColorsCrate};
3+
4+
/// ANSI color prefix strings for terminal output, initialized from the `LS_COLORS` environment
5+
/// variable.
6+
#[derive(Debug, Clone)]
7+
pub struct LsColors {
8+
directory: String,
9+
normal: String,
10+
executable: String,
11+
symlink: String,
12+
}
13+
14+
impl LsColors {
15+
/// Initialize by reading the current environment's `LS_COLORS`.
16+
pub fn from_env() -> Self {
17+
let ls_colors = LsColorsCrate::from_env().unwrap_or_default();
18+
let prefix_for = |indicator: Indicator| {
19+
ls_colors
20+
.style_for_indicator(indicator)
21+
.map(|s| s.to_nu_ansi_term_style().prefix().to_string())
22+
.unwrap_or_default()
23+
};
24+
LsColors {
25+
directory: prefix_for(Indicator::Directory),
26+
normal: prefix_for(Indicator::RegularFile),
27+
executable: prefix_for(Indicator::ExecutableFile),
28+
symlink: prefix_for(Indicator::SymbolicLink),
29+
}
30+
}
31+
32+
/// Return the ANSI prefix string for the given [`Color`] variant.
33+
pub(crate) fn prefix_str(&self, color: Color) -> &str {
34+
match color {
35+
Color::Directory => &self.directory,
36+
Color::Normal => &self.normal,
37+
Color::Executable => &self.executable,
38+
Color::Symlink => &self.symlink,
39+
}
40+
}
41+
}

src/visualizer/coloring.rs

Lines changed: 61 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,32 @@
1-
use crate::AnsiPrefixes;
1+
use super::{ChildPosition, TreeHorizontalSlice};
2+
use crate::ls_colors::LsColors;
3+
use derive_more::Display;
24
use std::{collections::HashMap, fmt, hash::Hash};
5+
use zero_copy_pads::Width;
6+
7+
/// Coloring configuration: ANSI prefix strings from the environment and a name-to-color map.
8+
#[derive(Debug)]
9+
pub struct Coloring<Name> {
10+
ansi_prefixes: LsColors,
11+
map: HashMap<Name, Color>,
12+
}
13+
14+
impl<Name: Hash + Eq> Coloring<Name> {
15+
/// Create a new [`Coloring`] from ANSI prefixes and a name-to-color map.
16+
pub fn new(ansi_prefixes: LsColors, map: HashMap<Name, Color>) -> Self {
17+
Coloring { ansi_prefixes, map }
18+
}
19+
20+
/// Return `(color, prefixes)` for a node, used to build a colored slice for rendering.
21+
pub(crate) fn node_color(&self, name: &Name, has_children: bool) -> Option<(Color, &LsColors)> {
22+
let color = if has_children {
23+
Some(Color::Directory)
24+
} else {
25+
self.map.get(name).copied()
26+
}?;
27+
Some((color, &self.ansi_prefixes))
28+
}
29+
}
330

431
/// The coloring to apply to a node name.
532
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -16,24 +43,14 @@ pub enum Color {
1643

1744
impl Color {
1845
/// 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-
})
46+
pub fn ansi_prefix(self, prefixes: &LsColors) -> AnsiPrefix<'_> {
47+
AnsiPrefix(prefixes.prefix_str(self))
2648
}
2749
}
2850

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-
}
51+
/// ANSI prefix wrapper for a [`Color`] variant, implements [`Display`].
52+
#[derive(Display)]
53+
pub struct AnsiPrefix<'a>(&'a str);
3754

3855
impl AnsiPrefix<'_> {
3956
/// Returns the reset suffix to emit after this prefix, or `""` if no prefix.
@@ -46,18 +63,35 @@ impl AnsiPrefix<'_> {
4663
}
4764
}
4865

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>,
66+
/// A [`TreeHorizontalSlice`] with its color applied, used for rendering.
67+
pub(crate) struct ColoredTreeHorizontalSlice<'a> {
68+
pub(crate) slice: TreeHorizontalSlice<String>,
69+
pub(crate) color: Color,
70+
pub(crate) ansi_prefixes: &'a LsColors,
5671
}
5772

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 }
73+
impl fmt::Display for ColoredTreeHorizontalSlice<'_> {
74+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75+
let TreeHorizontalSlice {
76+
ancestor_relative_positions,
77+
skeletal_component,
78+
name,
79+
} = &self.slice;
80+
for pos in ancestor_relative_positions {
81+
let connector = match pos {
82+
ChildPosition::Init => "│ ",
83+
ChildPosition::Last => " ",
84+
};
85+
write!(f, "{connector}")?;
86+
}
87+
let prefix = self.color.ansi_prefix(self.ansi_prefixes);
88+
let suffix = prefix.suffix();
89+
write!(f, "{skeletal_component}{prefix}{name}{suffix}")
90+
}
91+
}
92+
93+
impl Width for ColoredTreeHorizontalSlice<'_> {
94+
fn width(&self) -> usize {
95+
self.slice.width()
6296
}
6397
}

0 commit comments

Comments
 (0)