use std::path::Path;
use f00_core::{Entry, EntryKind};
use lscolors::{LsColors, Style};
use nu_ansi_term::Color;
#[derive(Clone)]
pub struct Colorizer {
enabled: bool,
ls: LsColors,
}
impl Colorizer {
pub fn new(enabled: bool) -> Self {
Self {
enabled,
ls: LsColors::from_env().unwrap_or_default(),
}
}
pub fn from_ls_colors(enabled: bool, ls_colors: &str) -> Self {
Self {
enabled,
ls: LsColors::from_string(ls_colors),
}
}
pub fn ls_colors(&self) -> &LsColors {
&self.ls
}
pub fn enabled(&self) -> bool {
self.enabled
}
pub fn paint_name(&self, entry: &Entry, text: &str) -> String {
if !self.enabled {
return text.to_string();
}
if let Some(style) = self.style_for_entry(entry) {
return paint_with_ls_style(text, style);
}
match entry.kind {
EntryKind::Directory => Color::Blue.bold().paint(text).to_string(),
EntryKind::Symlink => Color::Cyan.paint(text).to_string(),
EntryKind::File if entry_is_exec(entry) => Color::Green.bold().paint(text).to_string(),
EntryKind::File => text.to_string(),
EntryKind::Other => Color::Yellow.paint(text).to_string(),
}
}
pub fn style_for_entry<'a>(&'a self, entry: &'a Entry) -> Option<&'a Style> {
if let Some(style) = self.ls.style_for_path(&entry.path) {
return Some(style);
}
if let Some(style) = self.ls.style_for_path(Path::new(&entry.name)) {
return Some(style);
}
use lscolors::Indicator;
let indicator = match entry.kind {
EntryKind::Directory => Some(Indicator::Directory),
EntryKind::Symlink => Some(Indicator::SymbolicLink),
EntryKind::File if entry_is_exec(entry) => Some(Indicator::ExecutableFile),
EntryKind::File => Some(Indicator::RegularFile),
EntryKind::Other => {
#[cfg(unix)]
{
match entry.mode & 0o170000 {
0o010000 => Some(Indicator::FIFO),
0o140000 => Some(Indicator::Socket),
0o060000 => Some(Indicator::BlockDevice),
0o020000 => Some(Indicator::CharacterDevice),
_ => None,
}
}
#[cfg(not(unix))]
{
None
}
}
};
indicator.and_then(|i| self.ls.style_for_indicator(i))
}
pub fn paint_git_char(&self, ch: char) -> String {
if !self.enabled {
return ch.to_string();
}
let s = ch.to_string();
match ch {
'M' => Color::Yellow.bold().paint(s).to_string(),
'A' => Color::Green.bold().paint(s).to_string(),
'D' | 'U' => Color::Red.bold().paint(s).to_string(),
'?' => Color::Purple.paint(s).to_string(),
'!' => Color::DarkGray.paint(s).to_string(),
'R' => Color::Cyan.bold().paint(s).to_string(),
_ => s,
}
}
}
fn paint_with_ls_style(text: &str, style: &Style) -> String {
style.to_nu_ansi_term_style().paint(text).to_string()
}
fn entry_is_exec(entry: &Entry) -> bool {
#[cfg(unix)]
{
entry.mode & 0o111 != 0
}
#[cfg(not(unix))]
{
let _ = entry;
false
}
}
#[cfg(test)]
mod tests {
use super::*;
use f00_core::{Entry, EntryKind, GitStatus};
use std::path::PathBuf;
fn file(name: &str) -> Entry {
Entry {
path: PathBuf::from(name),
name: name.into(),
kind: EntryKind::File,
size: 0,
modified: None,
created: None,
accessed: None,
changed: None,
mode: 0o644,
readonly: false,
symlink_target: None,
depth: 0,
git_status: GitStatus::Clean,
is_dir_header: false,
nlink: 1,
uid: 0,
gid: 0,
inode: 0,
blocks: 0,
owner: "u".into(),
group: "g".into(),
author: "u".into(),
context: String::new(),
}
}
#[test]
fn disabled_no_ansi() {
let c = Colorizer::from_ls_colors(false, "*.rs=01;31");
let e = file("main.rs");
assert_eq!(c.paint_name(&e, "main.rs"), "main.rs");
}
#[test]
fn ls_colors_extension() {
let c = Colorizer::from_ls_colors(true, "*.rs=01;31:");
let e = file("main.rs");
let painted = c.paint_name(&e, "main.rs");
assert!(painted.contains("main.rs"), "{painted:?}");
let _ = c.style_for_entry(&e);
}
}