feat(ui): Make file types themeable

This commit is contained in:
Robert Minsk 2023-09-10 20:35:14 -07:00
parent f149fe6afb
commit b5174b1582
4 changed files with 91 additions and 50 deletions

View file

@ -7,11 +7,9 @@
//! # Contributors
//! Please keep these lists sorted. If you're using vim, :sort i
use ansiterm::Style;
use phf::{phf_map, Map};
use crate::fs::File;
use crate::theme::FileColours;
#[derive(Debug, Clone)]
pub enum FileType {
@ -269,7 +267,7 @@ impl FileType {
/// Lookup the file type based on the file's name, by the file name
/// lowercase extension, or if the file could be compiled from related
/// source code.
fn get_file_type(file: &File<'_>) -> Option<FileType> {
pub(crate) fn get_file_type(file: &File<'_>) -> Option<FileType> {
// Case-insensitive readme is checked first for backwards compatibility.
if file.name.to_lowercase().starts_with("readme") {
return Some(Self::Immediate)
@ -291,27 +289,3 @@ impl FileType {
None
}
}
#[derive(Debug)]
pub struct FileTypeColor;
impl FileColours for FileTypeColor {
/// Map from the file type to the display style/color for the file.
fn colour_file(&self, file: &File<'_>) -> Option<Style> {
use ansiterm::Colour::*;
match FileType::get_file_type(file) {
Some(FileType::Compiled) => Some(Yellow.normal()),
Some(FileType::Compressed) => Some(Red.normal()),
Some(FileType::Crypto) => Some(Green.bold()),
Some(FileType::Document) => Some(Green.normal()),
Some(FileType::Image) => Some(Purple.normal()),
Some(FileType::Immediate) => Some(Yellow.bold().underline()),
Some(FileType::Lossless) => Some(Cyan.bold()),
Some(FileType::Music) => Some(Cyan.normal()),
Some(FileType::Temp) => Some(White.normal()),
Some(FileType::Video) => Some(Purple.bold()),
_ => None
}
}
}

View file

@ -78,6 +78,19 @@ impl UiStyles {
},
},
file_type: FileType {
image: Purple.normal(),
video: Purple.bold(),
music: Cyan.normal(),
lossless: Cyan.bold(),
crypto: Green.bold(),
document: Green.normal(),
compressed: Red.normal(),
temp: White.normal(),
compiled: Yellow.normal(),
immediate: Yellow.bold().underline()
},
punctuation: DarkGray.bold(),
date: Blue.normal(),
inode: Purple.normal(),

View file

@ -1,6 +1,7 @@
use ansiterm::Style;
use crate::fs::File;
use crate::info::filetype::FileType;
use crate::output::file_name::Colours as FileNameColours;
use crate::output::render;
@ -65,8 +66,6 @@ impl Options {
#[allow(trivial_casts)] // the `as Box<_>` stuff below warns about this for some reason
pub fn to_theme(&self, isatty: bool) -> Theme {
use crate::info::filetype::FileTypeColor;
if self.use_colours == UseColours::Never || (self.use_colours == UseColours::Automatic && ! isatty) {
let ui = UiStyles::plain();
let exts = Box::new(NoFileColours);
@ -79,10 +78,10 @@ impl Options {
// Use between 0 and 2 file name highlighters
let exts = match (exts.is_non_empty(), use_default_filetypes) {
(false, false) => Box::new(NoFileColours) as Box<_>,
(false, true) => Box::new(FileTypeColor) as Box<_>,
( true, false) => Box::new(exts) as Box<_>,
( true, true) => Box::new((exts, FileTypeColor)) as Box<_>,
(false, false) => Box::new(NoFileColours) as Box<_>,
(false, true) => Box::new(FileTypes) as Box<_>,
( true, false) => Box::new(exts) as Box<_>,
( true, true) => Box::new((exts, FileTypes)) as Box<_>,
};
Theme { ui, exts }
@ -145,13 +144,13 @@ impl Definitions {
pub trait FileColours: std::marker::Sync {
fn colour_file(&self, file: &File<'_>) -> Option<Style>;
fn colour_file(&self, file: &File<'_>, theme: &Theme) -> Option<Style>;
}
#[derive(PartialEq, Debug)]
struct NoFileColours;
impl FileColours for NoFileColours {
fn colour_file(&self, _file: &File<'_>) -> Option<Style> {
fn colour_file(&self, _file: &File<'_>, _theme: &Theme) -> Option<Style> {
None
}
}
@ -164,9 +163,9 @@ impl<A, B> FileColours for (A, B)
where A: FileColours,
B: FileColours,
{
fn colour_file(&self, file: &File<'_>) -> Option<Style> {
self.0.colour_file(file)
.or_else(|| self.1.colour_file(file))
fn colour_file(&self, file: &File<'_>, theme: &Theme) -> Option<Style> {
self.0.colour_file(file, theme)
.or_else(|| self.1.colour_file(file, theme))
}
}
@ -176,17 +175,6 @@ struct ExtensionMappings {
mappings: Vec<(glob::Pattern, Style)>,
}
// Loop through backwards so that colours specified later in the list override
// colours specified earlier, like we do with options and strict mode
impl FileColours for ExtensionMappings {
fn colour_file(&self, file: &File<'_>) -> Option<Style> {
self.mappings.iter().rev()
.find(|t| t.0.matches(&file.name))
.map (|t| t.1)
}
}
impl ExtensionMappings {
fn is_non_empty(&self) -> bool {
! self.mappings.is_empty()
@ -197,8 +185,37 @@ impl ExtensionMappings {
}
}
// Loop through backwards so that colours specified later in the list override
// colours specified earlier, like we do with options and strict mode
impl FileColours for ExtensionMappings {
fn colour_file(&self, file: &File<'_>, _theme: &Theme) -> Option<Style> {
self.mappings.iter().rev()
.find(|t| t.0.matches(&file.name))
.map (|t| t.1)
}
}
#[derive(Debug)]
struct FileTypes;
impl FileColours for FileTypes {
fn colour_file(&self, file: &File<'_>, theme: &Theme) -> Option<Style> {
match FileType::get_file_type(file) {
Some(FileType::Image) => Some(theme.ui.file_type.image),
Some(FileType::Video) => Some(theme.ui.file_type.video),
Some(FileType::Music) => Some(theme.ui.file_type.music),
Some(FileType::Lossless) => Some(theme.ui.file_type.lossless),
Some(FileType::Crypto) => Some(theme.ui.file_type.crypto),
Some(FileType::Document) => Some(theme.ui.file_type.document),
Some(FileType::Compressed) => Some(theme.ui.file_type.compressed),
Some(FileType::Temp) => Some(theme.ui.file_type.temp),
Some(FileType::Compiled) => Some(theme.ui.file_type.compiled),
Some(FileType::Immediate) => Some(theme.ui.file_type.immediate),
None => None
}
}
}
#[cfg(unix)]
impl render::BlocksColours for Theme {
@ -330,7 +347,7 @@ impl FileNameColours for Theme {
fn mount_point(&self) -> Style { self.ui.filekinds.mount_point }
fn colour_file(&self, file: &File<'_>) -> Style {
self.exts.colour_file(file).unwrap_or(self.ui.filekinds.normal)
self.exts.colour_file(file, &self).unwrap_or(self.ui.filekinds.normal)
}
}
@ -537,6 +554,17 @@ mod customs_test {
test!(exa_mp: ls "", exa "mp=1;34;4" => colours c -> { c.filekinds.mount_point = Blue.bold().underline(); });
test!(exa_im: ls "", exa "im=38;5;128" => colours c -> { c.file_type.image = Fixed(128).normal(); });
test!(exa_vi: ls "", exa "vi=38;5;129" => colours c -> { c.file_type.video = Fixed(129).normal(); });
test!(exa_mu: ls "", exa "mu=38;5;130" => colours c -> { c.file_type.music = Fixed(130).normal(); });
test!(exa_lo: ls "", exa "lo=38;5;131" => colours c -> { c.file_type.lossless = Fixed(131).normal(); });
test!(exa_cr: ls "", exa "cr=38;5;132" => colours c -> { c.file_type.crypto = Fixed(132).normal(); });
test!(exa_do: ls "", exa "do=38;5;133" => colours c -> { c.file_type.document = Fixed(133).normal(); });
test!(exa_co: ls "", exa "co=38;5;134" => colours c -> { c.file_type.compressed = Fixed(134).normal(); });
test!(exa_tm: ls "", exa "tm=38;5;135" => colours c -> { c.file_type.temp = Fixed(135).normal(); });
test!(exa_cm: ls "", exa "cm=38;5;136" => colours c -> { c.file_type.compiled = Fixed(136).normal(); });
test!(exa_ie: ls "", exa "ie=38;5;137" => colours c -> { c.file_type.immediate = Fixed(137).normal(); });
// All the while, LS_COLORS treats them as filenames:
test!(ls_uu: ls "uu=38;5;117", exa "" => exts [ ("uu", Fixed(117).normal()) ]);
test!(ls_un: ls "un=38;5;118", exa "" => exts [ ("un", Fixed(118).normal()) ]);

View file

@ -14,6 +14,7 @@ pub struct UiStyles {
pub links: Links,
pub git: Git,
pub security_context: SecurityContext,
pub file_type: FileType,
pub punctuation: Style,
pub date: Style,
@ -121,6 +122,20 @@ pub struct SecurityContext {
pub selinux: SELinuxContext,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct FileType {
pub image: Style,
pub video: Style,
pub music: Style,
pub lossless: Style,
pub crypto: Style,
pub document: Style,
pub compressed: Style,
pub temp: Style,
pub compiled: Style,
pub immediate: Style,
}
impl UiStyles {
pub fn plain() -> Self {
Self::default()
@ -213,6 +228,17 @@ impl UiStyles {
"mp" => self.filekinds.mount_point = pair.to_style(),
"im" => self.file_type.image = pair.to_style(),
"vi" => self.file_type.video = pair.to_style(),
"mu" => self.file_type.music = pair.to_style(),
"lo" => self.file_type.lossless = pair.to_style(),
"cr" => self.file_type.crypto = pair.to_style(),
"do" => self.file_type.document = pair.to_style(),
"co" => self.file_type.compressed = pair.to_style(),
"tm" => self.file_type.temp = pair.to_style(),
"cm" => self.file_type.compiled = pair.to_style(),
"ie" => self.file_type.immediate = pair.to_style(),
_ => return false,
}