mirror of
https://github.com/Llewellynvdm/exa.git
synced 2024-12-26 01:57:32 +00:00
Merge branch 'view-options'
This commit is contained in:
commit
b05f18cae0
@ -7,8 +7,8 @@
|
||||
use ansi_term::Style;
|
||||
|
||||
use crate::fs::File;
|
||||
use crate::output::file_name::FileColours;
|
||||
use crate::output::icons::FileIcon;
|
||||
use crate::theme::FileColours;
|
||||
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
|
55
src/main.rs
55
src/main.rs
@ -35,13 +35,14 @@ use crate::fs::feature::git::GitCache;
|
||||
use crate::fs::filter::GitIgnore;
|
||||
use crate::options::{Options, Vars, vars, OptionsResult};
|
||||
use crate::output::{escape, lines, grid, grid_details, details, View, Mode};
|
||||
use crate::theme::Theme;
|
||||
|
||||
mod fs;
|
||||
mod info;
|
||||
mod logger;
|
||||
mod options;
|
||||
mod output;
|
||||
mod style;
|
||||
mod theme;
|
||||
|
||||
|
||||
fn main() {
|
||||
@ -61,7 +62,10 @@ fn main() {
|
||||
|
||||
let git = git_options(&options, &input_paths);
|
||||
let writer = io::stdout();
|
||||
let exa = Exa { options, writer, input_paths, git };
|
||||
|
||||
let console_width = options.view.width.actual_terminal_width();
|
||||
let theme = options.theme.to_theme(console_width.is_some());
|
||||
let exa = Exa { options, writer, input_paths, theme, console_width, git };
|
||||
|
||||
match exa.run() {
|
||||
Ok(exit_status) => {
|
||||
@ -114,6 +118,15 @@ pub struct Exa<'args> {
|
||||
/// names (anything that isn’t an option).
|
||||
pub input_paths: Vec<&'args OsStr>,
|
||||
|
||||
/// The theme that has been configured from the command-line options and
|
||||
/// environment variables. If colours are disabled, this is a theme with
|
||||
/// every style set to the default.
|
||||
pub theme: Theme,
|
||||
|
||||
/// The detected width of the console. This is used to determine which
|
||||
/// view to use.
|
||||
pub console_width: Option<usize>,
|
||||
|
||||
/// A global Git cache, if the option was passed in.
|
||||
/// This has to last the lifetime of the program, because the user might
|
||||
/// want to list several directories in the same repository.
|
||||
@ -241,45 +254,57 @@ impl<'args> Exa<'args> {
|
||||
}
|
||||
|
||||
/// Prints the list of files using whichever view is selected.
|
||||
/// For various annoying logistical reasons, each one handles
|
||||
/// printing differently...
|
||||
fn print_files(&mut self, dir: Option<&Dir>, files: Vec<File<'_>>) -> io::Result<()> {
|
||||
if files.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let View { ref mode, ref colours, ref style } = self.options.view;
|
||||
let theme = &self.theme;
|
||||
let View { ref mode, ref file_style, .. } = self.options.view;
|
||||
|
||||
match mode {
|
||||
Mode::Lines(ref opts) => {
|
||||
let r = lines::Render { files, colours, style, opts };
|
||||
match (mode, self.console_width) {
|
||||
(Mode::Grid(ref opts), Some(console_width)) => {
|
||||
let r = grid::Render { files, theme, file_style, opts, console_width };
|
||||
r.render(&mut self.writer)
|
||||
}
|
||||
|
||||
Mode::Grid(ref opts) => {
|
||||
let r = grid::Render { files, colours, style, opts };
|
||||
(Mode::Grid(_), None) |
|
||||
(Mode::Lines, _) => {
|
||||
let r = lines::Render { files, theme, file_style };
|
||||
r.render(&mut self.writer)
|
||||
}
|
||||
|
||||
Mode::Details(ref opts) => {
|
||||
(Mode::Details(ref opts), _) => {
|
||||
let filter = &self.options.filter;
|
||||
let recurse = self.options.dir_action.recurse_options();
|
||||
|
||||
let git_ignoring = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore;
|
||||
let git = self.git.as_ref();
|
||||
let r = details::Render { dir, files, colours, style, opts, filter, recurse, git_ignoring, git };
|
||||
let r = details::Render { dir, files, theme, file_style, opts, filter, recurse, git_ignoring, git };
|
||||
r.render(&mut self.writer)
|
||||
}
|
||||
|
||||
Mode::GridDetails(ref opts) => {
|
||||
(Mode::GridDetails(ref opts), Some(console_width)) => {
|
||||
let grid = &opts.grid;
|
||||
let filter = &self.options.filter;
|
||||
let details = &opts.details;
|
||||
let row_threshold = opts.row_threshold;
|
||||
|
||||
let filter = &self.options.filter;
|
||||
let git_ignoring = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore;
|
||||
let git = self.git.as_ref();
|
||||
let r = grid_details::Render { dir, files, colours, style, grid, details, filter, row_threshold, git_ignoring, git };
|
||||
|
||||
let r = grid_details::Render { dir, files, theme, file_style, grid, details, filter, row_threshold, git_ignoring, git, console_width };
|
||||
r.render(&mut self.writer)
|
||||
}
|
||||
|
||||
(Mode::GridDetails(ref opts), None) => {
|
||||
let opts = &opts.to_details_options();
|
||||
let filter = &self.options.filter;
|
||||
let recurse = self.options.dir_action.recurse_options();
|
||||
let git_ignoring = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore;
|
||||
|
||||
let git = self.git.as_ref();
|
||||
let r = details::Render { dir, files, theme, file_style, opts, filter, recurse, git_ignoring, git };
|
||||
r.render(&mut self.writer)
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ impl DirAction {
|
||||
/// There are three possible actions, and they overlap somewhat: the
|
||||
/// `--tree` flag is another form of recursion, so those two are allowed
|
||||
/// to both be present, but the `--list-dirs` flag is used separately.
|
||||
pub fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
|
||||
pub fn deduce(matches: &MatchedFlags<'_>, can_tree: bool) -> Result<Self, OptionsError> {
|
||||
let recurse = matches.has(&flags::RECURSE)?;
|
||||
let as_file = matches.has(&flags::LIST_DIRS)?;
|
||||
let tree = matches.has(&flags::TREE)?;
|
||||
@ -30,7 +30,9 @@ impl DirAction {
|
||||
}
|
||||
}
|
||||
|
||||
if tree {
|
||||
if tree && can_tree {
|
||||
// Tree is only appropriate in details mode, so this has to
|
||||
// examine the View, which should have already been deduced by now
|
||||
Ok(Self::Recurse(RecurseOptions::deduce(matches, true)?))
|
||||
}
|
||||
else if recurse {
|
||||
@ -83,7 +85,7 @@ mod test {
|
||||
use crate::options::test::Strictnesses::*;
|
||||
|
||||
static TEST_ARGS: &[&Arg] = &[&flags::RECURSE, &flags::LIST_DIRS, &flags::TREE, &flags::LEVEL ];
|
||||
for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf)) {
|
||||
for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf, true)) {
|
||||
assert_eq!(result, $result);
|
||||
}
|
||||
}
|
||||
|
23
src/options/file_name.rs
Normal file
23
src/options/file_name.rs
Normal file
@ -0,0 +1,23 @@
|
||||
use crate::options::{flags, OptionsError};
|
||||
use crate::options::parser::MatchedFlags;
|
||||
|
||||
use crate::output::file_name::{Options, Classify};
|
||||
|
||||
|
||||
impl Options {
|
||||
pub fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
|
||||
let classify = Classify::deduce(matches)?;
|
||||
let icons = matches.has(&flags::ICONS)?;
|
||||
|
||||
Ok(Self { classify, icons })
|
||||
}
|
||||
}
|
||||
|
||||
impl Classify {
|
||||
fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
|
||||
let flagged = matches.has(&flags::CLASSIFY)?;
|
||||
|
||||
if flagged { Ok(Self::AddFileIndicators) }
|
||||
else { Ok(Self::JustFilenames) }
|
||||
}
|
||||
}
|
@ -74,11 +74,13 @@ use std::ffi::OsStr;
|
||||
use crate::fs::dir_action::DirAction;
|
||||
use crate::fs::filter::{FileFilter, GitIgnore};
|
||||
use crate::output::{View, Mode, details, grid_details};
|
||||
use crate::theme::Options as ThemeOptions;
|
||||
|
||||
mod dir_action;
|
||||
mod file_name;
|
||||
mod filter;
|
||||
mod flags;
|
||||
mod style;
|
||||
mod theme;
|
||||
mod view;
|
||||
|
||||
mod error;
|
||||
@ -109,8 +111,14 @@ pub struct Options {
|
||||
/// How to sort and filter files before outputting them.
|
||||
pub filter: FileFilter,
|
||||
|
||||
/// The type of output to use (lines, grid, or details).
|
||||
/// The user’s preference of view to use (lines, grid, details, or
|
||||
/// grid-details) along with the options on how to render file names.
|
||||
/// If the view requires the terminal to have a width, and there is no
|
||||
/// width, then the view will be downgraded.
|
||||
pub view: View,
|
||||
|
||||
/// The options to make up the styles of the UI and file names.
|
||||
pub theme: ThemeOptions,
|
||||
}
|
||||
|
||||
impl Options {
|
||||
@ -168,11 +176,12 @@ impl Options {
|
||||
/// Determines the complete set of options based on the given command-line
|
||||
/// arguments, after they’ve been parsed.
|
||||
fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {
|
||||
let dir_action = DirAction::deduce(matches)?;
|
||||
let filter = FileFilter::deduce(matches)?;
|
||||
let view = View::deduce(matches, vars)?;
|
||||
let dir_action = DirAction::deduce(matches, matches!(view.mode, Mode::Details(_)))?;
|
||||
let filter = FileFilter::deduce(matches)?;
|
||||
let theme = ThemeOptions::deduce(matches, vars)?;
|
||||
|
||||
Ok(Self { dir_action, view, filter })
|
||||
Ok(Self { dir_action, filter, view, theme })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -394,13 +394,21 @@ impl<'a> MatchedFlags<'a> {
|
||||
else { Err(OptionsError::Duplicate(all[0].0, all[1].0)) }
|
||||
}
|
||||
else {
|
||||
let any = self.flags.iter().rev()
|
||||
.find(|tuple| tuple.1.is_none() && predicate(&tuple.0))
|
||||
.map(|tuple| &tuple.0);
|
||||
Ok(any)
|
||||
Ok(self.has_where_any(predicate))
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the first found argument that satisfies the predicate, or
|
||||
/// nothing if none is found, with strict mode having no effect.
|
||||
///
|
||||
/// You’ll have to test the resulting flag to see which argument it was.
|
||||
pub fn has_where_any<P>(&self, predicate: P) -> Option<&Flag>
|
||||
where P: Fn(&Flag) -> bool {
|
||||
self.flags.iter().rev()
|
||||
.find(|tuple| tuple.1.is_none() && predicate(&tuple.0))
|
||||
.map(|tuple| &tuple.0)
|
||||
}
|
||||
|
||||
// This code could probably be better.
|
||||
// Both ‘has’ and ‘get’ immediately begin with a conditional, which makes
|
||||
// me think the functionality could be moved to inside Strictness.
|
||||
|
159
src/options/theme.rs
Normal file
159
src/options/theme.rs
Normal file
@ -0,0 +1,159 @@
|
||||
use crate::options::{flags, vars, Vars, OptionsError};
|
||||
use crate::options::parser::MatchedFlags;
|
||||
use crate::theme::{Options, UseColours, ColourScale, Definitions};
|
||||
|
||||
|
||||
impl Options {
|
||||
pub fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {
|
||||
let use_colours = UseColours::deduce(matches)?;
|
||||
let colour_scale = ColourScale::deduce(matches)?;
|
||||
|
||||
let definitions = if use_colours == UseColours::Never {
|
||||
Definitions::default()
|
||||
}
|
||||
else {
|
||||
Definitions::deduce(vars)
|
||||
};
|
||||
|
||||
Ok(Self { use_colours, colour_scale, definitions })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl UseColours {
|
||||
fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
|
||||
let word = match matches.get_where(|f| f.matches(&flags::COLOR) || f.matches(&flags::COLOUR))? {
|
||||
Some(w) => w,
|
||||
None => return Ok(Self::Automatic),
|
||||
};
|
||||
|
||||
if word == "always" {
|
||||
Ok(Self::Always)
|
||||
}
|
||||
else if word == "auto" || word == "automatic" {
|
||||
Ok(Self::Automatic)
|
||||
}
|
||||
else if word == "never" {
|
||||
Ok(Self::Never)
|
||||
}
|
||||
else {
|
||||
Err(OptionsError::BadArgument(&flags::COLOR, word.into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl ColourScale {
|
||||
fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
|
||||
if matches.has_where(|f| f.matches(&flags::COLOR_SCALE) || f.matches(&flags::COLOUR_SCALE))?.is_some() {
|
||||
Ok(Self::Gradient)
|
||||
}
|
||||
else {
|
||||
Ok(Self::Fixed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Definitions {
|
||||
fn deduce<V: Vars>(vars: &V) -> Self {
|
||||
let ls = vars.get(vars::LS_COLORS) .map(|e| e.to_string_lossy().to_string());
|
||||
let exa = vars.get(vars::EXA_COLORS).map(|e| e.to_string_lossy().to_string());
|
||||
Self { ls, exa }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod terminal_test {
|
||||
use super::*;
|
||||
use std::ffi::OsString;
|
||||
use crate::options::flags;
|
||||
use crate::options::parser::{Flag, Arg};
|
||||
|
||||
use crate::options::test::parse_for_test;
|
||||
use crate::options::test::Strictnesses::*;
|
||||
|
||||
static TEST_ARGS: &[&Arg] = &[ &flags::COLOR, &flags::COLOUR,
|
||||
&flags::COLOR_SCALE, &flags::COLOUR_SCALE, ];
|
||||
|
||||
macro_rules! test {
|
||||
($name:ident: $type:ident <- $inputs:expr; $stricts:expr => $result:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf)) {
|
||||
assert_eq!(result, $result);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
($name:ident: $type:ident <- $inputs:expr; $stricts:expr => err $result:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf)) {
|
||||
assert_eq!(result.unwrap_err(), $result);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
struct MockVars {
|
||||
ls: &'static str,
|
||||
exa: &'static str,
|
||||
}
|
||||
|
||||
// Test impl that just returns the value it has.
|
||||
impl Vars for MockVars {
|
||||
fn get(&self, name: &'static str) -> Option<OsString> {
|
||||
if name == vars::LS_COLORS && ! self.ls.is_empty() {
|
||||
Some(OsString::from(self.ls.clone()))
|
||||
}
|
||||
else if name == vars::EXA_COLORS && ! self.exa.is_empty() {
|
||||
Some(OsString::from(self.exa.clone()))
|
||||
}
|
||||
else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Default
|
||||
test!(empty: UseColours <- []; Both => Ok(UseColours::Automatic));
|
||||
|
||||
// --colour
|
||||
test!(u_always: UseColours <- ["--colour=always"]; Both => Ok(UseColours::Always));
|
||||
test!(u_auto: UseColours <- ["--colour", "auto"]; Both => Ok(UseColours::Automatic));
|
||||
test!(u_never: UseColours <- ["--colour=never"]; Both => Ok(UseColours::Never));
|
||||
|
||||
// --color
|
||||
test!(no_u_always: UseColours <- ["--color", "always"]; Both => Ok(UseColours::Always));
|
||||
test!(no_u_auto: UseColours <- ["--color=auto"]; Both => Ok(UseColours::Automatic));
|
||||
test!(no_u_never: UseColours <- ["--color", "never"]; Both => Ok(UseColours::Never));
|
||||
|
||||
// Errors
|
||||
test!(no_u_error: UseColours <- ["--color=upstream"]; Both => err OptionsError::BadArgument(&flags::COLOR, OsString::from("upstream"))); // the error is for --color
|
||||
test!(u_error: UseColours <- ["--colour=lovers"]; Both => err OptionsError::BadArgument(&flags::COLOR, OsString::from("lovers"))); // and so is this one!
|
||||
|
||||
// Overriding
|
||||
test!(overridden_1: UseColours <- ["--colour=auto", "--colour=never"]; Last => Ok(UseColours::Never));
|
||||
test!(overridden_2: UseColours <- ["--color=auto", "--colour=never"]; Last => Ok(UseColours::Never));
|
||||
test!(overridden_3: UseColours <- ["--colour=auto", "--color=never"]; Last => Ok(UseColours::Never));
|
||||
test!(overridden_4: UseColours <- ["--color=auto", "--color=never"]; Last => Ok(UseColours::Never));
|
||||
|
||||
test!(overridden_5: UseColours <- ["--colour=auto", "--colour=never"]; Complain => err OptionsError::Duplicate(Flag::Long("colour"), Flag::Long("colour")));
|
||||
test!(overridden_6: UseColours <- ["--color=auto", "--colour=never"]; Complain => err OptionsError::Duplicate(Flag::Long("color"), Flag::Long("colour")));
|
||||
test!(overridden_7: UseColours <- ["--colour=auto", "--color=never"]; Complain => err OptionsError::Duplicate(Flag::Long("colour"), Flag::Long("color")));
|
||||
test!(overridden_8: UseColours <- ["--color=auto", "--color=never"]; Complain => err OptionsError::Duplicate(Flag::Long("color"), Flag::Long("color")));
|
||||
|
||||
test!(scale_1: ColourScale <- ["--color-scale", "--colour-scale"]; Last => Ok(ColourScale::Gradient));
|
||||
test!(scale_2: ColourScale <- ["--color-scale", ]; Last => Ok(ColourScale::Gradient));
|
||||
test!(scale_3: ColourScale <- [ "--colour-scale"]; Last => Ok(ColourScale::Gradient));
|
||||
test!(scale_4: ColourScale <- [ ]; Last => Ok(ColourScale::Fixed));
|
||||
|
||||
test!(scale_5: ColourScale <- ["--color-scale", "--colour-scale"]; Complain => err OptionsError::Duplicate(Flag::Long("color-scale"), Flag::Long("colour-scale")));
|
||||
test!(scale_6: ColourScale <- ["--color-scale", ]; Complain => Ok(ColourScale::Gradient));
|
||||
test!(scale_7: ColourScale <- [ "--colour-scale"]; Complain => Ok(ColourScale::Gradient));
|
||||
test!(scale_8: ColourScale <- [ ]; Complain => Ok(ColourScale::Fixed));
|
||||
}
|
@ -1,120 +1,86 @@
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
use crate::fs::feature::xattr;
|
||||
use crate::options::{flags, OptionsError, Vars};
|
||||
use crate::options::parser::MatchedFlags;
|
||||
use crate::output::{View, Mode, grid, details, lines};
|
||||
use crate::output::{View, Mode, TerminalWidth, grid, details};
|
||||
use crate::output::grid_details::{self, RowThreshold};
|
||||
use crate::output::file_name::Options as FileStyle;
|
||||
use crate::output::table::{TimeTypes, SizeFormat, Columns, Options as TableOptions};
|
||||
use crate::output::time::TimeFormat;
|
||||
|
||||
|
||||
impl View {
|
||||
|
||||
/// Determine which view to use and all of that view’s arguments.
|
||||
pub fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {
|
||||
use crate::options::style::Styles;
|
||||
|
||||
let mode = Mode::deduce(matches, vars)?;
|
||||
let Styles { colours, style } = Styles::deduce(matches, vars, || *TERM_WIDTH)?;
|
||||
Ok(Self { mode, colours, style })
|
||||
let width = TerminalWidth::deduce(vars)?;
|
||||
let file_style = FileStyle::deduce(matches)?;
|
||||
Ok(Self { mode, width, file_style })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Mode {
|
||||
|
||||
/// Determine the mode from the command-line arguments.
|
||||
/// Determine which viewing mode to use based on the user’s options.
|
||||
///
|
||||
/// As with the other options, arguments are scanned right-to-left and the
|
||||
/// first flag found is matched, so `exa --oneline --long` will pick a
|
||||
/// details view, and `exa --long --oneline` will pick the lines view.
|
||||
///
|
||||
/// This is complicated a little by the fact that `--grid` and `--tree`
|
||||
/// can also combine with `--long`, so care has to be taken to use the
|
||||
pub fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {
|
||||
let long = || {
|
||||
if matches.is_strict() && matches.has(&flags::ACROSS)? && ! matches.has(&flags::GRID)? {
|
||||
Err(OptionsError::Useless(&flags::ACROSS, true, &flags::LONG))
|
||||
}
|
||||
else if matches.is_strict() && matches.has(&flags::ONE_LINE)? {
|
||||
Err(OptionsError::Useless(&flags::ONE_LINE, true, &flags::LONG))
|
||||
}
|
||||
else {
|
||||
Ok(details::Options {
|
||||
table: Some(TableOptions::deduce(matches, vars)?),
|
||||
header: matches.has(&flags::HEADER)?,
|
||||
xattr: xattr::ENABLED && matches.has(&flags::EXTENDED)?,
|
||||
icons: matches.has(&flags::ICONS)?,
|
||||
})
|
||||
let flag = matches.has_where_any(|f| f.matches(&flags::LONG) || f.matches(&flags::ONE_LINE)
|
||||
|| f.matches(&flags::GRID) || f.matches(&flags::TREE));
|
||||
|
||||
let flag = match flag {
|
||||
Some(f) => f,
|
||||
None => {
|
||||
Self::strict_check_long_flags(matches)?;
|
||||
let grid = grid::Options::deduce(matches)?;
|
||||
return Ok(Self::Grid(grid));
|
||||
}
|
||||
};
|
||||
|
||||
let other_options_scan = || {
|
||||
if let Some(width) = TerminalWidth::deduce(vars)?.width() {
|
||||
if matches.has(&flags::ONE_LINE)? {
|
||||
if matches.is_strict() && matches.has(&flags::ACROSS)? {
|
||||
Err(OptionsError::Useless(&flags::ACROSS, true, &flags::ONE_LINE))
|
||||
}
|
||||
else {
|
||||
let lines = lines::Options { icons: matches.has(&flags::ICONS)? };
|
||||
Ok(Self::Lines(lines))
|
||||
}
|
||||
}
|
||||
else if matches.has(&flags::TREE)? {
|
||||
let details = details::Options {
|
||||
table: None,
|
||||
header: false,
|
||||
xattr: xattr::ENABLED && matches.has(&flags::EXTENDED)?,
|
||||
icons: matches.has(&flags::ICONS)?,
|
||||
};
|
||||
if flag.matches(&flags::LONG)
|
||||
|| (flag.matches(&flags::TREE) && matches.has(&flags::LONG)?)
|
||||
|| (flag.matches(&flags::GRID) && matches.has(&flags::LONG)?)
|
||||
{
|
||||
let _ = matches.has(&flags::LONG)?;
|
||||
let details = details::Options::deduce_long(matches, vars)?;
|
||||
|
||||
Ok(Self::Details(details))
|
||||
}
|
||||
else {
|
||||
let grid = grid::Options {
|
||||
across: matches.has(&flags::ACROSS)?,
|
||||
console_width: width,
|
||||
icons: matches.has(&flags::ICONS)?,
|
||||
};
|
||||
let flag = matches.has_where_any(|f| f.matches(&flags::GRID) || f.matches(&flags::TREE));
|
||||
|
||||
Ok(Self::Grid(grid))
|
||||
}
|
||||
}
|
||||
|
||||
// If the terminal width couldn’t be matched for some reason, such
|
||||
// as the program’s stdout being connected to a file, then
|
||||
// fallback to the lines or details view.
|
||||
else if matches.has(&flags::TREE)? {
|
||||
let details = details::Options {
|
||||
table: None,
|
||||
header: false,
|
||||
xattr: xattr::ENABLED && matches.has(&flags::EXTENDED)?,
|
||||
icons: matches.has(&flags::ICONS)?,
|
||||
};
|
||||
|
||||
Ok(Self::Details(details))
|
||||
}
|
||||
else if matches.has(&flags::LONG)? {
|
||||
let details = long()?;
|
||||
Ok(Self::Details(details))
|
||||
} else {
|
||||
let lines = lines::Options { icons: matches.has(&flags::ICONS)?, };
|
||||
Ok(Self::Lines(lines))
|
||||
}
|
||||
};
|
||||
|
||||
if matches.has(&flags::LONG)? {
|
||||
let details = long()?;
|
||||
if matches.has(&flags::GRID)? {
|
||||
let other_options_mode = other_options_scan()?;
|
||||
if let Self::Grid(grid) = other_options_mode {
|
||||
let row_threshold = RowThreshold::deduce(vars)?;
|
||||
let opts = grid_details::Options { grid, details, row_threshold };
|
||||
return Ok(Self::GridDetails(opts));
|
||||
}
|
||||
else {
|
||||
return Ok(other_options_mode);
|
||||
}
|
||||
if flag.is_some() && flag.unwrap().matches(&flags::GRID) {
|
||||
let _ = matches.has(&flags::GRID)?;
|
||||
let grid = grid::Options::deduce(matches)?;
|
||||
let row_threshold = RowThreshold::deduce(vars)?;
|
||||
let grid_details = grid_details::Options { grid, details, row_threshold };
|
||||
return Ok(Self::GridDetails(grid_details));
|
||||
}
|
||||
else {
|
||||
// the --tree case is handled by the DirAction parser later
|
||||
return Ok(Self::Details(details));
|
||||
}
|
||||
}
|
||||
|
||||
Self::strict_check_long_flags(matches)?;
|
||||
|
||||
if flag.matches(&flags::TREE) {
|
||||
let _ = matches.has(&flags::TREE)?;
|
||||
let details = details::Options::deduce_tree(matches)?;
|
||||
return Ok(Self::Details(details));
|
||||
}
|
||||
|
||||
if flag.matches(&flags::ONE_LINE) {
|
||||
let _ = matches.has(&flags::ONE_LINE)?;
|
||||
return Ok(Self::Lines);
|
||||
}
|
||||
|
||||
let grid = grid::Options::deduce(matches)?;
|
||||
Ok(Self::Grid(grid))
|
||||
}
|
||||
|
||||
fn strict_check_long_flags(matches: &MatchedFlags<'_>) -> Result<(), OptionsError> {
|
||||
// If --long hasn’t been passed, then check if we need to warn the
|
||||
// user about flags that won’t have any effect.
|
||||
if matches.is_strict() {
|
||||
@ -129,36 +95,57 @@ impl Mode {
|
||||
return Err(OptionsError::Useless(&flags::GIT, false, &flags::LONG));
|
||||
}
|
||||
else if matches.has(&flags::LEVEL)? && ! matches.has(&flags::RECURSE)? && ! matches.has(&flags::TREE)? {
|
||||
// TODO: I’m not sure if the code even gets this far.
|
||||
// There is an identical check in dir_action
|
||||
return Err(OptionsError::Useless2(&flags::LEVEL, &flags::RECURSE, &flags::TREE));
|
||||
}
|
||||
}
|
||||
|
||||
other_options_scan()
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// The width of the terminal requested by the user.
|
||||
#[derive(PartialEq, Debug, Copy, Clone)]
|
||||
enum TerminalWidth {
|
||||
impl grid::Options {
|
||||
fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
|
||||
let grid = grid::Options {
|
||||
across: matches.has(&flags::ACROSS)?,
|
||||
};
|
||||
|
||||
/// The user requested this specific number of columns.
|
||||
Set(usize),
|
||||
|
||||
/// The terminal was found to have this number of columns.
|
||||
Terminal(usize),
|
||||
|
||||
/// The user didn’t request any particular terminal width.
|
||||
Unset,
|
||||
Ok(grid)
|
||||
}
|
||||
}
|
||||
|
||||
impl TerminalWidth {
|
||||
|
||||
/// Determine a requested terminal width from the command-line arguments.
|
||||
///
|
||||
/// Returns an error if a requested width doesn’t parse to an integer.
|
||||
impl details::Options {
|
||||
fn deduce_tree(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
|
||||
let details = details::Options {
|
||||
table: None,
|
||||
header: false,
|
||||
xattr: xattr::ENABLED && matches.has(&flags::EXTENDED)?,
|
||||
};
|
||||
|
||||
Ok(details)
|
||||
}
|
||||
|
||||
fn deduce_long<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {
|
||||
if matches.is_strict() {
|
||||
if matches.has(&flags::ACROSS)? && ! matches.has(&flags::GRID)? {
|
||||
return Err(OptionsError::Useless(&flags::ACROSS, true, &flags::LONG));
|
||||
}
|
||||
else if matches.has(&flags::ONE_LINE)? {
|
||||
return Err(OptionsError::Useless(&flags::ONE_LINE, true, &flags::LONG));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(details::Options {
|
||||
table: Some(TableOptions::deduce(matches, vars)?),
|
||||
header: matches.has(&flags::HEADER)?,
|
||||
xattr: xattr::ENABLED && matches.has(&flags::EXTENDED)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl TerminalWidth {
|
||||
fn deduce<V: Vars>(vars: &V) -> Result<Self, OptionsError> {
|
||||
use crate::options::vars;
|
||||
|
||||
@ -168,28 +155,14 @@ impl TerminalWidth {
|
||||
Err(e) => Err(OptionsError::FailedParse(e)),
|
||||
}
|
||||
}
|
||||
else if let Some(width) = *TERM_WIDTH {
|
||||
Ok(Self::Terminal(width))
|
||||
}
|
||||
else {
|
||||
Ok(Self::Unset)
|
||||
}
|
||||
}
|
||||
|
||||
fn width(self) -> Option<usize> {
|
||||
match self {
|
||||
Self::Set(width) |
|
||||
Self::Terminal(width) => Some(width),
|
||||
Self::Unset => None,
|
||||
Ok(Self::Automatic)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl RowThreshold {
|
||||
|
||||
/// Determine whether to use a row threshold based on the given
|
||||
/// environment variables.
|
||||
fn deduce<V: Vars>(vars: &V) -> Result<Self, OptionsError> {
|
||||
use crate::options::vars;
|
||||
|
||||
@ -357,20 +330,6 @@ impl TimeTypes {
|
||||
}
|
||||
|
||||
|
||||
// Gets, then caches, the width of the terminal that exa is running in.
|
||||
// This gets used multiple times above, with no real guarantee of order,
|
||||
// so it’s easier to just cache it the first time it runs.
|
||||
lazy_static! {
|
||||
static ref TERM_WIDTH: Option<usize> = {
|
||||
// All of stdin, stdout, and stderr could not be connected to a
|
||||
// terminal, but we’re only interested in stdout because it’s
|
||||
// where the output goes.
|
||||
use term_size::dimensions_stdout;
|
||||
dimensions_stdout().map(|t| t.0)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
@ -383,10 +342,10 @@ mod test {
|
||||
|
||||
static TEST_ARGS: &[&Arg] = &[ &flags::BINARY, &flags::BYTES, &flags::TIME_STYLE,
|
||||
&flags::TIME, &flags::MODIFIED, &flags::CHANGED,
|
||||
&flags::CREATED, &flags::ACCESSED, &flags::ICONS,
|
||||
&flags::CREATED, &flags::ACCESSED,
|
||||
&flags::HEADER, &flags::GROUP, &flags::INODE, &flags::GIT,
|
||||
&flags::LINKS, &flags::BLOCKS, &flags::LONG, &flags::LEVEL,
|
||||
&flags::GRID, &flags::ACROSS, &flags::ONE_LINE ];
|
||||
&flags::GRID, &flags::ACROSS, &flags::ONE_LINE, &flags::TREE ];
|
||||
|
||||
macro_rules! test {
|
||||
|
||||
@ -561,7 +520,6 @@ mod test {
|
||||
use super::*;
|
||||
|
||||
use crate::output::grid::Options as GridOptions;
|
||||
use crate::output::lines::Options as LineOptions;
|
||||
|
||||
|
||||
// Default
|
||||
@ -572,12 +530,10 @@ mod test {
|
||||
test!(grid: Mode <- ["--grid"], None; Both => like Ok(Mode::Grid(GridOptions { across: false, .. })));
|
||||
test!(across: Mode <- ["--across"], None; Both => like Ok(Mode::Grid(GridOptions { across: true, .. })));
|
||||
test!(gracross: Mode <- ["-xG"], None; Both => like Ok(Mode::Grid(GridOptions { across: true, .. })));
|
||||
test!(icons: Mode <- ["--icons"], None; Both => like Ok(Mode::Grid(GridOptions { icons: true, .. })));
|
||||
|
||||
// Lines views
|
||||
test!(lines: Mode <- ["--oneline"], None; Both => like Ok(Mode::Lines(LineOptions { .. })));
|
||||
test!(prima: Mode <- ["-1"], None; Both => like Ok(Mode::Lines(LineOptions { .. })));
|
||||
test!(line_icon: Mode <- ["-1", "--icons"], None; Both => like Ok(Mode::Lines(LineOptions { icons: true })));
|
||||
test!(lines: Mode <- ["--oneline"], None; Both => like Ok(Mode::Lines));
|
||||
test!(prima: Mode <- ["-1"], None; Both => like Ok(Mode::Lines));
|
||||
|
||||
// Details views
|
||||
test!(long: Mode <- ["--long"], None; Both => like Ok(Mode::Details(_)));
|
||||
@ -589,7 +545,6 @@ mod test {
|
||||
|
||||
// Options that do nothing with --long
|
||||
test!(long_across: Mode <- ["--long", "--across"], None; Last => like Ok(Mode::Details(_)));
|
||||
test!(long_oneline: Mode <- ["--long", "--oneline"], None; Last => like Ok(Mode::Details(_)));
|
||||
|
||||
// Options that do nothing without --long
|
||||
test!(just_header: Mode <- ["--header"], None; Last => like Ok(Mode::Grid(_)));
|
||||
@ -613,5 +568,14 @@ mod test {
|
||||
|
||||
#[cfg(feature = "git")]
|
||||
test!(just_git_2: Mode <- ["--git"], None; Complain => err OptionsError::Useless(&flags::GIT, false, &flags::LONG));
|
||||
|
||||
// Contradictions and combinations
|
||||
test!(lgo: Mode <- ["--long", "--grid", "--oneline"], None; Both => like Ok(Mode::Lines));
|
||||
test!(lgt: Mode <- ["--long", "--grid", "--tree"], None; Both => like Ok(Mode::Details(_)));
|
||||
test!(tgl: Mode <- ["--tree", "--grid", "--long"], None; Both => like Ok(Mode::GridDetails(_)));
|
||||
test!(tlg: Mode <- ["--tree", "--long", "--grid"], None; Both => like Ok(Mode::GridDetails(_)));
|
||||
test!(ot: Mode <- ["--oneline", "--tree"], None; Both => like Ok(Mode::Details(_)));
|
||||
test!(og: Mode <- ["--oneline", "--grid"], None; Both => like Ok(Mode::Grid(_)));
|
||||
test!(tg: Mode <- ["--tree", "--grid"], None; Both => like Ok(Mode::Grid(_)));
|
||||
}
|
||||
}
|
||||
|
@ -73,12 +73,12 @@ use crate::fs::dir_action::RecurseOptions;
|
||||
use crate::fs::feature::git::GitCache;
|
||||
use crate::fs::feature::xattr::{Attribute, FileAttributes};
|
||||
use crate::fs::filter::FileFilter;
|
||||
use crate::style::Colours;
|
||||
use crate::output::cell::TextCell;
|
||||
use crate::output::icons::painted_icon;
|
||||
use crate::output::file_name::FileStyle;
|
||||
use crate::output::file_name::Options as FileStyle;
|
||||
use crate::output::table::{Table, Options as TableOptions, Row as TableRow};
|
||||
use crate::output::tree::{TreeTrunk, TreeParams, TreeDepth};
|
||||
use crate::theme::Theme;
|
||||
|
||||
|
||||
/// With the **Details** view, the output gets formatted into columns, with
|
||||
@ -106,17 +106,14 @@ pub struct Options {
|
||||
|
||||
/// Whether to show each file’s extended attributes.
|
||||
pub xattr: bool,
|
||||
|
||||
/// Whether icons mode is enabled.
|
||||
pub icons: bool,
|
||||
}
|
||||
|
||||
|
||||
pub struct Render<'a> {
|
||||
pub dir: Option<&'a Dir>,
|
||||
pub files: Vec<File<'a>>,
|
||||
pub colours: &'a Colours,
|
||||
pub style: &'a FileStyle,
|
||||
pub theme: &'a Theme,
|
||||
pub file_style: &'a FileStyle,
|
||||
pub opts: &'a Options,
|
||||
|
||||
/// Whether to recurse through directories with a tree view, and if so,
|
||||
@ -162,7 +159,7 @@ impl<'a> Render<'a> {
|
||||
(None, _) => {/* Keep Git how it is */},
|
||||
}
|
||||
|
||||
let mut table = Table::new(table, self.git, self.colours);
|
||||
let mut table = Table::new(table, self.git, &self.theme);
|
||||
|
||||
if self.opts.header {
|
||||
let header = table.header_row();
|
||||
@ -256,18 +253,21 @@ impl<'a> Render<'a> {
|
||||
}
|
||||
|
||||
let mut dir = None;
|
||||
|
||||
if let Some(r) = self.recurse {
|
||||
if file.is_directory() && r.tree && ! r.is_too_deep(depth.0) {
|
||||
match file.to_dir() {
|
||||
Ok(d) => { dir = Some(d); },
|
||||
Err(e) => { errors.push((e, None)) },
|
||||
Ok(d) => {
|
||||
dir = Some(d);
|
||||
}
|
||||
Err(e) => {
|
||||
errors.push((e, None));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let icon = if self.opts.icons { Some(painted_icon(file, self.style)) }
|
||||
else { None };
|
||||
let icon = if self.file_style.icons { Some(painted_icon(file, self.theme)) }
|
||||
else { None };
|
||||
|
||||
let egg = Egg { table_row, xattrs, errors, dir, file, icon };
|
||||
unsafe { std::ptr::write(file_eggs.lock().unwrap()[idx].as_mut_ptr(), egg) }
|
||||
@ -292,7 +292,7 @@ impl<'a> Render<'a> {
|
||||
name_cell.push(ANSIGenericString::from(icon), 2)
|
||||
}
|
||||
|
||||
let style = self.style.for_file(egg.file, self.colours)
|
||||
let style = self.file_style.for_file(egg.file, self.theme)
|
||||
.with_link_paths()
|
||||
.paint()
|
||||
.promote();
|
||||
@ -355,7 +355,7 @@ impl<'a> Render<'a> {
|
||||
Row {
|
||||
tree: TreeParams::new(TreeDepth::root(), false),
|
||||
cells: Some(header),
|
||||
name: TextCell::paint_str(self.colours.header, "Name"),
|
||||
name: TextCell::paint_str(self.theme.ui.header, "Name"),
|
||||
}
|
||||
}
|
||||
|
||||
@ -369,12 +369,12 @@ impl<'a> Render<'a> {
|
||||
|
||||
// TODO: broken_symlink() doesn’t quite seem like the right name for
|
||||
// the style that’s being used here. Maybe split it in two?
|
||||
let name = TextCell::paint(self.colours.broken_symlink(), error_message);
|
||||
let name = TextCell::paint(self.theme.broken_symlink(), error_message);
|
||||
Row { cells: None, name, tree }
|
||||
}
|
||||
|
||||
fn render_xattr(&self, xattr: &Attribute, tree: TreeParams) -> Row {
|
||||
let name = TextCell::paint(self.colours.perms.attribute, format!("{} (len {})", xattr.name, xattr.size));
|
||||
let name = TextCell::paint(self.theme.ui.perms.attribute, format!("{} (len {})", xattr.name, xattr.size));
|
||||
Row { cells: None, name, tree }
|
||||
}
|
||||
|
||||
@ -388,7 +388,7 @@ impl<'a> Render<'a> {
|
||||
total_width: table.widths().total(),
|
||||
table,
|
||||
inner: rows.into_iter(),
|
||||
tree_style: self.colours.punctuation,
|
||||
tree_style: self.theme.ui.punctuation,
|
||||
}
|
||||
}
|
||||
|
||||
@ -396,7 +396,7 @@ impl<'a> Render<'a> {
|
||||
Iter {
|
||||
tree_trunk: TreeTrunk::default(),
|
||||
inner: rows.into_iter(),
|
||||
tree_style: self.colours.punctuation,
|
||||
tree_style: self.theme.ui.punctuation,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
use std::fmt::Debug;
|
||||
use std::marker::Sync;
|
||||
use std::path::Path;
|
||||
|
||||
use ansi_term::{ANSIString, Style};
|
||||
@ -11,34 +10,32 @@ use crate::output::render::FiletypeColours;
|
||||
|
||||
|
||||
/// Basically a file name factory.
|
||||
#[derive(Debug)]
|
||||
pub struct FileStyle {
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct Options {
|
||||
|
||||
/// Whether to append file class characters to file names.
|
||||
pub classify: Classify,
|
||||
|
||||
/// Mapping of file extensions to colours, to highlight regular files.
|
||||
pub exts: Box<dyn FileColours>,
|
||||
/// Whether to prepend icon characters before file names.
|
||||
pub icons: bool,
|
||||
}
|
||||
|
||||
impl FileStyle {
|
||||
impl Options {
|
||||
|
||||
/// Create a new `FileName` that prints the given file’s name, painting it
|
||||
/// with the remaining arguments.
|
||||
pub fn for_file<'a, 'dir, C: Colours>(&'a self, file: &'a File<'dir>, colours: &'a C) -> FileName<'a, 'dir, C> {
|
||||
pub fn for_file<'a, 'dir, C>(self, file: &'a File<'dir>, colours: &'a C) -> FileName<'a, 'dir, C> {
|
||||
FileName {
|
||||
file,
|
||||
colours,
|
||||
link_style: LinkStyle::JustFilenames,
|
||||
classify: self.classify,
|
||||
exts: &*self.exts,
|
||||
options: self,
|
||||
target: if file.is_link() { Some(file.link_target()) }
|
||||
else { None }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// When displaying a file name, there needs to be some way to handle broken
|
||||
/// links, depending on how long the resulting Cell can be.
|
||||
#[derive(PartialEq, Debug, Copy, Clone)]
|
||||
@ -76,7 +73,7 @@ impl Default for Classify {
|
||||
|
||||
/// A **file name** holds all the information necessary to display the name
|
||||
/// of the given file. This is used in all of the views.
|
||||
pub struct FileName<'a, 'dir, C: Colours> {
|
||||
pub struct FileName<'a, 'dir, C> {
|
||||
|
||||
/// A reference to the file that we’re getting the name of.
|
||||
file: &'a File<'dir>,
|
||||
@ -85,20 +82,15 @@ pub struct FileName<'a, 'dir, C: Colours> {
|
||||
colours: &'a C,
|
||||
|
||||
/// The file that this file points to if it’s a link.
|
||||
target: Option<FileTarget<'dir>>,
|
||||
target: Option<FileTarget<'dir>>, // todo: remove?
|
||||
|
||||
/// How to handle displaying links.
|
||||
link_style: LinkStyle,
|
||||
|
||||
/// Whether to append file class characters to file names.
|
||||
classify: Classify,
|
||||
|
||||
/// Mapping of file extensions to colours, to highlight regular files.
|
||||
exts: &'a dyn FileColours,
|
||||
options: Options,
|
||||
}
|
||||
|
||||
|
||||
impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
|
||||
impl<'a, 'dir, C> FileName<'a, 'dir, C> {
|
||||
|
||||
/// Sets the flag on this file name to display link targets with an
|
||||
/// arrow followed by their path.
|
||||
@ -106,6 +98,9 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
|
||||
self.link_style = LinkStyle::FullLinkPaths;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
|
||||
|
||||
/// Paints the name of the file using the colours, resulting in a vector
|
||||
/// of coloured cells that can be printed to the terminal.
|
||||
@ -146,13 +141,17 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
|
||||
}
|
||||
|
||||
if ! target.name.is_empty() {
|
||||
let target_options = Options {
|
||||
classify: Classify::JustFilenames,
|
||||
icons: false,
|
||||
};
|
||||
|
||||
let target = FileName {
|
||||
file: target,
|
||||
colours: self.colours,
|
||||
target: None,
|
||||
link_style: LinkStyle::FullLinkPaths,
|
||||
classify: Classify::JustFilenames,
|
||||
exts: self.exts,
|
||||
options: target_options,
|
||||
};
|
||||
|
||||
for bit in target.coloured_file_name() {
|
||||
@ -179,7 +178,7 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
|
||||
}
|
||||
}
|
||||
}
|
||||
else if let Classify::AddFileIndicators = self.classify {
|
||||
else if let Classify::AddFileIndicators = self.options.classify {
|
||||
if let Some(class) = self.classify_char() {
|
||||
bits.push(Style::default().paint(class));
|
||||
}
|
||||
@ -188,7 +187,6 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
|
||||
bits.into()
|
||||
}
|
||||
|
||||
|
||||
/// Adds the bits of the parent path to the given bits vector.
|
||||
/// The path gets its characters escaped based on the colours.
|
||||
fn add_parent_bits(&self, bits: &mut Vec<ANSIString<'_>>, parent: &Path) {
|
||||
@ -208,7 +206,6 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// The character to be displayed after a file when classifying is on, if
|
||||
/// the file’s type has one associated with it.
|
||||
fn classify_char(&self) -> Option<&'static str> {
|
||||
@ -232,7 +229,6 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Returns at least one ANSI-highlighted string representing this file’s
|
||||
/// name using the given set of colours.
|
||||
///
|
||||
@ -257,7 +253,6 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
|
||||
bits
|
||||
}
|
||||
|
||||
|
||||
/// Figures out which colour to paint the filename part of the output,
|
||||
/// depending on which “type” of file it appears to be — either from the
|
||||
/// class on the filesystem or from its name. (Or the broken link colour,
|
||||
@ -271,13 +266,7 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
|
||||
}
|
||||
}
|
||||
|
||||
self.kind_style()
|
||||
.or_else(|| self.exts.colour_file(self.file))
|
||||
.unwrap_or_else(|| self.colours.normal())
|
||||
}
|
||||
|
||||
fn kind_style(&self) -> Option<Style> {
|
||||
Some(match self.file {
|
||||
match self.file {
|
||||
f if f.is_directory() => self.colours.directory(),
|
||||
f if f.is_executable_file() => self.colours.executable_file(),
|
||||
f if f.is_link() => self.colours.symlink(),
|
||||
@ -286,8 +275,8 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
|
||||
f if f.is_char_device() => self.colours.char_device(),
|
||||
f if f.is_socket() => self.colours.socket(),
|
||||
f if ! f.is_file() => self.colours.special(),
|
||||
_ => return None,
|
||||
})
|
||||
_ => self.colours.colour_file(self.file),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -319,33 +308,6 @@ pub trait Colours: FiletypeColours {
|
||||
|
||||
/// The style to paint a file that has its executable bit set.
|
||||
fn executable_file(&self) -> Style;
|
||||
}
|
||||
|
||||
|
||||
// needs Debug because FileStyle derives it
|
||||
pub trait FileColours: Debug + Sync {
|
||||
fn colour_file(&self, file: &File<'_>) -> Option<Style>;
|
||||
}
|
||||
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub struct NoFileColours;
|
||||
impl FileColours for NoFileColours {
|
||||
fn colour_file(&self, _file: &File<'_>) -> Option<Style> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// When getting the colour of a file from a *pair* of colourisers, try the
|
||||
// first one then try the second one. This lets the user provide their own
|
||||
// file type associations, while falling back to the default set if not set
|
||||
// explicitly.
|
||||
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<'_>) -> Style;
|
||||
}
|
||||
|
@ -4,20 +4,18 @@ use term_grid as tg;
|
||||
|
||||
use crate::fs::File;
|
||||
use crate::output::cell::DisplayWidth;
|
||||
use crate::output::file_name::FileStyle;
|
||||
use crate::output::file_name::Options as FileStyle;
|
||||
use crate::output::icons::painted_icon;
|
||||
use crate::style::Colours;
|
||||
use crate::theme::Theme;
|
||||
|
||||
|
||||
#[derive(PartialEq, Debug, Copy, Clone)]
|
||||
pub struct Options {
|
||||
pub across: bool,
|
||||
pub console_width: usize,
|
||||
pub icons: bool,
|
||||
}
|
||||
|
||||
impl Options {
|
||||
pub fn direction(&self) -> tg::Direction {
|
||||
pub fn direction(self) -> tg::Direction {
|
||||
if self.across { tg::Direction::LeftToRight }
|
||||
else { tg::Direction::TopToBottom }
|
||||
}
|
||||
@ -26,9 +24,10 @@ impl Options {
|
||||
|
||||
pub struct Render<'a> {
|
||||
pub files: Vec<File<'a>>,
|
||||
pub colours: &'a Colours,
|
||||
pub style: &'a FileStyle,
|
||||
pub theme: &'a Theme,
|
||||
pub file_style: &'a FileStyle,
|
||||
pub opts: &'a Options,
|
||||
pub console_width: usize,
|
||||
}
|
||||
|
||||
impl<'a> Render<'a> {
|
||||
@ -41,13 +40,13 @@ impl<'a> Render<'a> {
|
||||
grid.reserve(self.files.len());
|
||||
|
||||
for file in &self.files {
|
||||
let icon = if self.opts.icons { Some(painted_icon(file, self.style)) }
|
||||
else { None };
|
||||
let icon = if self.file_style.icons { Some(painted_icon(file, self.theme)) }
|
||||
else { None };
|
||||
|
||||
let filename = self.style.for_file(file, self.colours).paint();
|
||||
let filename = self.file_style.for_file(file, self.theme).paint();
|
||||
|
||||
let width = if self.opts.icons { DisplayWidth::from(2) + filename.width() }
|
||||
else { filename.width() };
|
||||
let width = if self.file_style.icons { DisplayWidth::from(2) + filename.width() }
|
||||
else { filename.width() };
|
||||
|
||||
grid.add(tg::Cell {
|
||||
contents: format!("{}{}", &icon.unwrap_or_default(), filename.strings()),
|
||||
@ -55,7 +54,7 @@ impl<'a> Render<'a> {
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(display) = grid.fit_into_width(self.opts.console_width) {
|
||||
if let Some(display) = grid.fit_into_width(self.console_width) {
|
||||
write!(w, "{}", display)
|
||||
}
|
||||
else {
|
||||
@ -63,11 +62,11 @@ impl<'a> Render<'a> {
|
||||
// This isn’t *quite* the same as the lines view, which also
|
||||
// displays full link paths.
|
||||
for file in &self.files {
|
||||
if self.opts.icons {
|
||||
write!(w, "{}", painted_icon(file, self.style))?;
|
||||
if self.file_style.icons {
|
||||
write!(w, "{}", painted_icon(file, self.theme))?;
|
||||
}
|
||||
|
||||
let name_cell = self.style.for_file(file, self.colours).paint();
|
||||
let name_cell = self.file_style.for_file(file, self.theme).paint();
|
||||
writeln!(w, "{}", name_cell.strings())?;
|
||||
}
|
||||
|
||||
|
@ -11,12 +11,12 @@ use crate::fs::feature::xattr::FileAttributes;
|
||||
use crate::fs::filter::FileFilter;
|
||||
use crate::output::cell::TextCell;
|
||||
use crate::output::details::{Options as DetailsOptions, Row as DetailsRow, Render as DetailsRender};
|
||||
use crate::output::file_name::FileStyle;
|
||||
use crate::output::file_name::Options as FileStyle;
|
||||
use crate::output::grid::Options as GridOptions;
|
||||
use crate::output::icons::painted_icon;
|
||||
use crate::output::table::{Table, Row as TableRow, Options as TableOptions};
|
||||
use crate::output::tree::{TreeParams, TreeDepth};
|
||||
use crate::style::Colours;
|
||||
use crate::theme::Theme;
|
||||
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
@ -26,6 +26,13 @@ pub struct Options {
|
||||
pub row_threshold: RowThreshold,
|
||||
}
|
||||
|
||||
impl Options {
|
||||
pub fn to_details_options(&self) -> &DetailsOptions {
|
||||
&self.details
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// The grid-details view can be configured to revert to just a details view
|
||||
/// (with one column) if it wouldn’t produce enough rows of output.
|
||||
///
|
||||
@ -56,10 +63,10 @@ pub struct Render<'a> {
|
||||
pub files: Vec<File<'a>>,
|
||||
|
||||
/// How to colour various pieces of text.
|
||||
pub colours: &'a Colours,
|
||||
pub theme: &'a Theme,
|
||||
|
||||
/// How to format filenames.
|
||||
pub style: &'a FileStyle,
|
||||
pub file_style: &'a FileStyle,
|
||||
|
||||
/// The grid part of the grid-details view.
|
||||
pub grid: &'a GridOptions,
|
||||
@ -80,6 +87,8 @@ pub struct Render<'a> {
|
||||
pub git_ignoring: bool,
|
||||
|
||||
pub git: Option<&'a GitCache>,
|
||||
|
||||
pub console_width: usize,
|
||||
}
|
||||
|
||||
impl<'a> Render<'a> {
|
||||
@ -90,12 +99,12 @@ impl<'a> Render<'a> {
|
||||
/// This includes an empty files vector because the files get added to
|
||||
/// the table in *this* file, not in details: we only want to insert every
|
||||
/// *n* files into each column’s table, not all of them.
|
||||
pub fn details(&self) -> DetailsRender<'a> {
|
||||
fn details_for_column(&self) -> DetailsRender<'a> {
|
||||
DetailsRender {
|
||||
dir: self.dir,
|
||||
files: Vec::new(),
|
||||
colours: self.colours,
|
||||
style: self.style,
|
||||
theme: self.theme,
|
||||
file_style: self.file_style,
|
||||
opts: self.details,
|
||||
recurse: None,
|
||||
filter: self.filter,
|
||||
@ -105,13 +114,15 @@ impl<'a> Render<'a> {
|
||||
}
|
||||
|
||||
/// Create a Details render for when this grid-details render doesn’t fit
|
||||
/// in the terminal (or something has gone wrong) and we have given up.
|
||||
/// in the terminal (or something has gone wrong) and we have given up, or
|
||||
/// when the user asked for a grid-details view but the terminal width is
|
||||
/// not available, so we downgrade.
|
||||
pub fn give_up(self) -> DetailsRender<'a> {
|
||||
DetailsRender {
|
||||
dir: self.dir,
|
||||
files: self.files,
|
||||
colours: self.colours,
|
||||
style: self.style,
|
||||
theme: self.theme,
|
||||
file_style: self.file_style,
|
||||
opts: self.details,
|
||||
recurse: None,
|
||||
filter: self.filter,
|
||||
@ -135,7 +146,7 @@ impl<'a> Render<'a> {
|
||||
pub fn find_fitting_grid(&mut self) -> Option<(grid::Grid, grid::Width)> {
|
||||
let options = self.details.table.as_ref().expect("Details table options not given!");
|
||||
|
||||
let drender = self.details();
|
||||
let drender = self.details_for_column();
|
||||
|
||||
let (first_table, _) = self.make_table(options, &drender);
|
||||
|
||||
@ -145,15 +156,15 @@ impl<'a> Render<'a> {
|
||||
|
||||
let file_names = self.files.iter()
|
||||
.map(|file| {
|
||||
if self.details.icons {
|
||||
if self.file_style.icons {
|
||||
let mut icon_cell = TextCell::default();
|
||||
icon_cell.push(ANSIGenericString::from(painted_icon(file, self.style)), 2);
|
||||
let file_cell = self.style.for_file(file, self.colours).paint().promote();
|
||||
icon_cell.push(ANSIGenericString::from(painted_icon(file, self.theme)), 2);
|
||||
let file_cell = self.file_style.for_file(file, self.theme).paint().promote();
|
||||
icon_cell.append(file_cell);
|
||||
icon_cell
|
||||
}
|
||||
else {
|
||||
self.style.for_file(file, self.colours).paint().promote()
|
||||
self.file_style.for_file(file, self.theme).paint().promote()
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
@ -167,7 +178,7 @@ impl<'a> Render<'a> {
|
||||
|
||||
let the_grid_fits = {
|
||||
let d = grid.fit_into_columns(column_count);
|
||||
d.is_complete() && d.width() <= self.grid.console_width
|
||||
d.is_complete() && d.width() <= self.console_width
|
||||
};
|
||||
|
||||
if the_grid_fits {
|
||||
@ -197,7 +208,7 @@ impl<'a> Render<'a> {
|
||||
(None, _) => {/* Keep Git how it is */},
|
||||
}
|
||||
|
||||
let mut table = Table::new(options, self.git, self.colours);
|
||||
let mut table = Table::new(options, self.git, &self.theme);
|
||||
let mut rows = Vec::new();
|
||||
|
||||
if self.details.header {
|
||||
|
@ -2,7 +2,7 @@ use ansi_term::Style;
|
||||
|
||||
use crate::fs::File;
|
||||
use crate::info::filetype::FileExtensions;
|
||||
use crate::output::file_name::FileStyle;
|
||||
use crate::theme::Theme;
|
||||
|
||||
|
||||
pub trait FileIcon {
|
||||
@ -28,26 +28,27 @@ impl Icons {
|
||||
}
|
||||
|
||||
|
||||
pub fn painted_icon(file: &File<'_>, style: &FileStyle) -> String {
|
||||
pub fn painted_icon(file: &File<'_>, theme: &Theme) -> String {
|
||||
use crate::output::file_name::Colours;
|
||||
|
||||
let file_icon = icon(file).to_string();
|
||||
let painted = style.exts
|
||||
.colour_file(file)
|
||||
.map_or(file_icon.to_string(), |c| {
|
||||
// Remove underline from icon
|
||||
if c.is_underline {
|
||||
match c.foreground {
|
||||
Some(color) => {
|
||||
Style::from(color).paint(file_icon).to_string()
|
||||
}
|
||||
None => {
|
||||
Style::default().paint(file_icon).to_string()
|
||||
}
|
||||
let c = theme.colour_file(file);
|
||||
|
||||
// Remove underline from icon
|
||||
let painted =
|
||||
if c.is_underline {
|
||||
match c.foreground {
|
||||
Some(color) => {
|
||||
Style::from(color).paint(file_icon).to_string()
|
||||
}
|
||||
None => {
|
||||
Style::default().paint(file_icon).to_string()
|
||||
}
|
||||
}
|
||||
else {
|
||||
c.paint(file_icon).to_string()
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
c.paint(file_icon).to_string()
|
||||
};
|
||||
|
||||
format!("{} ", painted)
|
||||
}
|
||||
|
@ -3,33 +3,27 @@ use std::io::{self, Write};
|
||||
use ansi_term::{ANSIStrings, ANSIGenericString};
|
||||
|
||||
use crate::fs::File;
|
||||
use crate::output::cell::TextCell;
|
||||
use crate::output::file_name::{FileName, FileStyle};
|
||||
use crate::output::cell::{TextCell, TextCellContents};
|
||||
use crate::output::file_name::{Options as FileStyle};
|
||||
use crate::output::icons::painted_icon;
|
||||
use crate::style::Colours;
|
||||
use crate::theme::Theme;
|
||||
|
||||
|
||||
#[derive(PartialEq, Debug, Copy, Clone)]
|
||||
pub struct Options {
|
||||
pub icons: bool,
|
||||
}
|
||||
|
||||
/// The lines view literally just displays each file, line-by-line.
|
||||
pub struct Render<'a> {
|
||||
pub files: Vec<File<'a>>,
|
||||
pub colours: &'a Colours,
|
||||
pub style: &'a FileStyle,
|
||||
pub opts: &'a Options,
|
||||
pub theme: &'a Theme,
|
||||
pub file_style: &'a FileStyle,
|
||||
}
|
||||
|
||||
impl<'a> Render<'a> {
|
||||
pub fn render<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
||||
for file in &self.files {
|
||||
let name_cell = self.render_file(file).paint();
|
||||
if self.opts.icons {
|
||||
let name_cell = self.render_file(file);
|
||||
if self.file_style.icons {
|
||||
// Create a TextCell for the icon then append the text to it
|
||||
let mut cell = TextCell::default();
|
||||
let icon = painted_icon(file, self.style);
|
||||
let icon = painted_icon(file, self.theme);
|
||||
cell.push(ANSIGenericString::from(icon), 2);
|
||||
cell.append(name_cell.promote());
|
||||
writeln!(w, "{}", ANSIStrings(&cell))?;
|
||||
@ -42,7 +36,10 @@ impl<'a> Render<'a> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn render_file<'f>(&self, file: &'f File<'a>) -> FileName<'f, 'a, Colours> {
|
||||
self.style.for_file(file, self.colours).with_link_paths()
|
||||
fn render_file<'f>(&self, file: &'f File<'a>) -> TextCellContents {
|
||||
self.file_style
|
||||
.for_file(file, self.theme)
|
||||
.with_link_paths()
|
||||
.paint()
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,3 @@
|
||||
use crate::output::file_name::FileStyle;
|
||||
use crate::style::Colours;
|
||||
|
||||
pub use self::cell::{TextCell, TextCellContents, DisplayWidth};
|
||||
pub use self::escape::escape;
|
||||
|
||||
@ -23,8 +20,8 @@ mod tree;
|
||||
#[derive(Debug)]
|
||||
pub struct View {
|
||||
pub mode: Mode,
|
||||
pub colours: Colours,
|
||||
pub style: FileStyle,
|
||||
pub width: TerminalWidth,
|
||||
pub file_style: file_name::Options,
|
||||
}
|
||||
|
||||
|
||||
@ -35,5 +32,30 @@ pub enum Mode {
|
||||
Grid(grid::Options),
|
||||
Details(details::Options),
|
||||
GridDetails(grid_details::Options),
|
||||
Lines(lines::Options),
|
||||
Lines,
|
||||
}
|
||||
|
||||
|
||||
/// The width of the terminal requested by the user.
|
||||
#[derive(PartialEq, Debug, Copy, Clone)]
|
||||
pub enum TerminalWidth {
|
||||
|
||||
/// The user requested this specific number of columns.
|
||||
Set(usize),
|
||||
|
||||
/// Look up the terminal size at runtime.
|
||||
Automatic,
|
||||
}
|
||||
|
||||
impl TerminalWidth {
|
||||
pub fn actual_terminal_width(self) -> Option<usize> {
|
||||
// All of stdin, stdout, and stderr could not be connected to a
|
||||
// terminal, but we’re only interested in stdout because it’s
|
||||
// where the output goes.
|
||||
|
||||
match self {
|
||||
Self::Set(width) => Some(width),
|
||||
Self::Automatic => term_size::dimensions_stdout().map(|t| t.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ use crate::fs::feature::git::GitCache;
|
||||
use crate::output::cell::TextCell;
|
||||
use crate::output::render::TimeRender;
|
||||
use crate::output::time::TimeFormat;
|
||||
use crate::style::Colours;
|
||||
use crate::theme::Theme;
|
||||
|
||||
|
||||
/// Options for displaying a table.
|
||||
@ -306,7 +306,7 @@ lazy_static! {
|
||||
|
||||
pub struct Table<'a> {
|
||||
columns: Vec<Column>,
|
||||
colours: &'a Colours,
|
||||
theme: &'a Theme,
|
||||
env: &'a Environment,
|
||||
widths: TableWidths,
|
||||
time_format: TimeFormat,
|
||||
@ -320,13 +320,13 @@ pub struct Row {
|
||||
}
|
||||
|
||||
impl<'a, 'f> Table<'a> {
|
||||
pub fn new(options: &'a Options, git: Option<&'a GitCache>, colours: &'a Colours) -> Table<'a> {
|
||||
pub fn new(options: &'a Options, git: Option<&'a GitCache>, theme: &'a Theme) -> Table<'a> {
|
||||
let columns = options.columns.collect(git.is_some());
|
||||
let widths = TableWidths::zero(columns.len());
|
||||
let env = &*ENVIRONMENT;
|
||||
|
||||
Table {
|
||||
colours,
|
||||
theme,
|
||||
widths,
|
||||
columns,
|
||||
git,
|
||||
@ -342,7 +342,7 @@ impl<'a, 'f> Table<'a> {
|
||||
|
||||
pub fn header_row(&self) -> Row {
|
||||
let cells = self.columns.iter()
|
||||
.map(|c| TextCell::paint_str(self.colours.header, c.header()))
|
||||
.map(|c| TextCell::paint_str(self.theme.ui.header, c.header()))
|
||||
.collect();
|
||||
|
||||
Row { cells }
|
||||
@ -377,44 +377,44 @@ impl<'a, 'f> Table<'a> {
|
||||
fn display(&self, file: &File<'_>, column: Column, xattrs: bool) -> TextCell {
|
||||
match column {
|
||||
Column::Permissions => {
|
||||
self.permissions_plus(file, xattrs).render(self.colours)
|
||||
self.permissions_plus(file, xattrs).render(self.theme)
|
||||
}
|
||||
Column::FileSize => {
|
||||
file.size().render(self.colours, self.size_format, &self.env.numeric)
|
||||
file.size().render(self.theme, self.size_format, &self.env.numeric)
|
||||
}
|
||||
Column::HardLinks => {
|
||||
file.links().render(self.colours, &self.env.numeric)
|
||||
file.links().render(self.theme, &self.env.numeric)
|
||||
}
|
||||
Column::Inode => {
|
||||
file.inode().render(self.colours.inode)
|
||||
file.inode().render(self.theme.ui.inode)
|
||||
}
|
||||
Column::Blocks => {
|
||||
file.blocks().render(self.colours)
|
||||
file.blocks().render(self.theme)
|
||||
}
|
||||
Column::User => {
|
||||
file.user().render(self.colours, &*self.env.lock_users())
|
||||
file.user().render(self.theme, &*self.env.lock_users())
|
||||
}
|
||||
Column::Group => {
|
||||
file.group().render(self.colours, &*self.env.lock_users())
|
||||
file.group().render(self.theme, &*self.env.lock_users())
|
||||
}
|
||||
Column::GitStatus => {
|
||||
self.git_status(file).render(self.colours)
|
||||
self.git_status(file).render(self.theme)
|
||||
}
|
||||
Column::Octal => {
|
||||
self.octal_permissions(file).render(self.colours.octal)
|
||||
self.octal_permissions(file).render(self.theme.ui.octal)
|
||||
}
|
||||
|
||||
Column::Timestamp(TimeType::Modified) => {
|
||||
file.modified_time().render(self.colours.date, &self.env.tz, self.time_format)
|
||||
file.modified_time().render(self.theme.ui.date, &self.env.tz, self.time_format)
|
||||
}
|
||||
Column::Timestamp(TimeType::Changed) => {
|
||||
file.changed_time().render(self.colours.date, &self.env.tz, self.time_format)
|
||||
file.changed_time().render(self.theme.ui.date, &self.env.tz, self.time_format)
|
||||
}
|
||||
Column::Timestamp(TimeType::Created) => {
|
||||
file.created_time().render(self.colours.date, &self.env.tz, self.time_format)
|
||||
file.created_time().render(self.theme.ui.date, &self.env.tz, self.time_format)
|
||||
}
|
||||
Column::Timestamp(TimeType::Accessed) => {
|
||||
file.accessed_time().render(self.colours.date, &self.env.tz, self.time_format)
|
||||
file.accessed_time().render(self.theme.ui.date, &self.env.tz, self.time_format)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,471 +0,0 @@
|
||||
use ansi_term::Colour::{Red, Green, Yellow, Blue, Cyan, Purple, Fixed};
|
||||
use ansi_term::Style;
|
||||
|
||||
use crate::output::file_name::Colours as FileNameColours;
|
||||
use crate::output::render;
|
||||
use crate::style::lsc::Pair;
|
||||
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub struct Colours {
|
||||
pub colourful: bool,
|
||||
|
||||
pub filekinds: FileKinds,
|
||||
pub perms: Permissions,
|
||||
pub size: Size,
|
||||
pub users: Users,
|
||||
pub links: Links,
|
||||
pub git: Git,
|
||||
|
||||
pub punctuation: Style,
|
||||
pub date: Style,
|
||||
pub inode: Style,
|
||||
pub blocks: Style,
|
||||
pub header: Style,
|
||||
pub octal: Style,
|
||||
|
||||
pub symlink_path: Style,
|
||||
pub control_char: Style,
|
||||
pub broken_symlink: Style,
|
||||
pub broken_path_overlay: Style,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub struct FileKinds {
|
||||
pub normal: Style,
|
||||
pub directory: Style,
|
||||
pub symlink: Style,
|
||||
pub pipe: Style,
|
||||
pub block_device: Style,
|
||||
pub char_device: Style,
|
||||
pub socket: Style,
|
||||
pub special: Style,
|
||||
pub executable: Style,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub struct Permissions {
|
||||
pub user_read: Style,
|
||||
pub user_write: Style,
|
||||
pub user_execute_file: Style,
|
||||
pub user_execute_other: Style,
|
||||
|
||||
pub group_read: Style,
|
||||
pub group_write: Style,
|
||||
pub group_execute: Style,
|
||||
|
||||
pub other_read: Style,
|
||||
pub other_write: Style,
|
||||
pub other_execute: Style,
|
||||
|
||||
pub special_user_file: Style,
|
||||
pub special_other: Style,
|
||||
|
||||
pub attribute: Style,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub struct Size {
|
||||
pub major: Style,
|
||||
pub minor: Style,
|
||||
|
||||
pub number_byte: Style,
|
||||
pub number_kilo: Style,
|
||||
pub number_mega: Style,
|
||||
pub number_giga: Style,
|
||||
pub number_huge: Style,
|
||||
|
||||
pub unit_byte: Style,
|
||||
pub unit_kilo: Style,
|
||||
pub unit_mega: Style,
|
||||
pub unit_giga: Style,
|
||||
pub unit_huge: Style,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub struct Users {
|
||||
pub user_you: Style,
|
||||
pub user_someone_else: Style,
|
||||
pub group_yours: Style,
|
||||
pub group_not_yours: Style,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub struct Links {
|
||||
pub normal: Style,
|
||||
pub multi_link_file: Style,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub struct Git {
|
||||
pub new: Style,
|
||||
pub modified: Style,
|
||||
pub deleted: Style,
|
||||
pub renamed: Style,
|
||||
pub typechange: Style,
|
||||
pub ignored: Style,
|
||||
pub conflicted: Style,
|
||||
}
|
||||
|
||||
impl Colours {
|
||||
pub fn plain() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn colourful(scale: bool) -> Self {
|
||||
Self {
|
||||
colourful: true,
|
||||
|
||||
filekinds: FileKinds {
|
||||
normal: Style::default(),
|
||||
directory: Blue.bold(),
|
||||
symlink: Cyan.normal(),
|
||||
pipe: Yellow.normal(),
|
||||
block_device: Yellow.bold(),
|
||||
char_device: Yellow.bold(),
|
||||
socket: Red.bold(),
|
||||
special: Yellow.normal(),
|
||||
executable: Green.bold(),
|
||||
},
|
||||
|
||||
perms: Permissions {
|
||||
user_read: Yellow.bold(),
|
||||
user_write: Red.bold(),
|
||||
user_execute_file: Green.bold().underline(),
|
||||
user_execute_other: Green.bold(),
|
||||
|
||||
group_read: Yellow.normal(),
|
||||
group_write: Red.normal(),
|
||||
group_execute: Green.normal(),
|
||||
|
||||
other_read: Yellow.normal(),
|
||||
other_write: Red.normal(),
|
||||
other_execute: Green.normal(),
|
||||
|
||||
special_user_file: Purple.normal(),
|
||||
special_other: Purple.normal(),
|
||||
|
||||
attribute: Style::default(),
|
||||
},
|
||||
|
||||
size: Size::colourful(scale),
|
||||
|
||||
users: Users {
|
||||
user_you: Yellow.bold(),
|
||||
user_someone_else: Style::default(),
|
||||
group_yours: Yellow.bold(),
|
||||
group_not_yours: Style::default(),
|
||||
},
|
||||
|
||||
links: Links {
|
||||
normal: Red.bold(),
|
||||
multi_link_file: Red.on(Yellow),
|
||||
},
|
||||
|
||||
git: Git {
|
||||
new: Green.normal(),
|
||||
modified: Blue.normal(),
|
||||
deleted: Red.normal(),
|
||||
renamed: Yellow.normal(),
|
||||
typechange: Purple.normal(),
|
||||
ignored: Style::default().dimmed(),
|
||||
conflicted: Red.normal(),
|
||||
},
|
||||
|
||||
punctuation: Fixed(244).normal(),
|
||||
date: Blue.normal(),
|
||||
inode: Purple.normal(),
|
||||
blocks: Cyan.normal(),
|
||||
octal: Purple.normal(),
|
||||
header: Style::default().underline(),
|
||||
|
||||
symlink_path: Cyan.normal(),
|
||||
control_char: Red.normal(),
|
||||
broken_symlink: Red.normal(),
|
||||
broken_path_overlay: Style::default().underline(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Size {
|
||||
pub fn colourful(scale: bool) -> Self {
|
||||
if scale { Self::colourful_scale() }
|
||||
else { Self::colourful_plain() }
|
||||
}
|
||||
|
||||
fn colourful_plain() -> Self {
|
||||
Self {
|
||||
major: Green.bold(),
|
||||
minor: Green.normal(),
|
||||
|
||||
number_byte: Green.bold(),
|
||||
number_kilo: Green.bold(),
|
||||
number_mega: Green.bold(),
|
||||
number_giga: Green.bold(),
|
||||
number_huge: Green.bold(),
|
||||
|
||||
unit_byte: Green.normal(),
|
||||
unit_kilo: Green.normal(),
|
||||
unit_mega: Green.normal(),
|
||||
unit_giga: Green.normal(),
|
||||
unit_huge: Green.normal(),
|
||||
}
|
||||
}
|
||||
|
||||
fn colourful_scale() -> Self {
|
||||
Self {
|
||||
major: Green.bold(),
|
||||
minor: Green.normal(),
|
||||
|
||||
number_byte: Fixed(118).normal(),
|
||||
number_kilo: Fixed(190).normal(),
|
||||
number_mega: Fixed(226).normal(),
|
||||
number_giga: Fixed(220).normal(),
|
||||
number_huge: Fixed(214).normal(),
|
||||
|
||||
unit_byte: Green.normal(),
|
||||
unit_kilo: Green.normal(),
|
||||
unit_mega: Green.normal(),
|
||||
unit_giga: Green.normal(),
|
||||
unit_huge: Green.normal(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Some of the styles are **overlays**: although they have the same attribute
|
||||
/// set as regular styles (foreground and background colours, bold, underline,
|
||||
/// etc), they’re intended to be used to *amend* existing styles.
|
||||
///
|
||||
/// For example, the target path of a broken symlink is displayed in a red,
|
||||
/// underlined style by default. Paths can contain control characters, so
|
||||
/// these control characters need to be underlined too, otherwise it looks
|
||||
/// weird. So instead of having four separate configurable styles for “link
|
||||
/// path”, “broken link path”, “control character” and “broken control
|
||||
/// character”, there are styles for “link path”, “control character”, and
|
||||
/// “broken link overlay”, the latter of which is just set to override the
|
||||
/// underline attribute on the other two.
|
||||
fn apply_overlay(mut base: Style, overlay: Style) -> Style {
|
||||
if let Some(fg) = overlay.foreground { base.foreground = Some(fg); }
|
||||
if let Some(bg) = overlay.background { base.background = Some(bg); }
|
||||
|
||||
if overlay.is_bold { base.is_bold = true; }
|
||||
if overlay.is_dimmed { base.is_dimmed = true; }
|
||||
if overlay.is_italic { base.is_italic = true; }
|
||||
if overlay.is_underline { base.is_underline = true; }
|
||||
if overlay.is_blink { base.is_blink = true; }
|
||||
if overlay.is_reverse { base.is_reverse = true; }
|
||||
if overlay.is_hidden { base.is_hidden = true; }
|
||||
if overlay.is_strikethrough { base.is_strikethrough = true; }
|
||||
|
||||
base
|
||||
}
|
||||
// TODO: move this function to the ansi_term crate
|
||||
|
||||
|
||||
impl Colours {
|
||||
|
||||
/// Sets a value on this set of colours using one of the keys understood
|
||||
/// by the `LS_COLORS` environment variable. Invalid keys set nothing, but
|
||||
/// return false.
|
||||
pub fn set_ls(&mut self, pair: &Pair<'_>) -> bool {
|
||||
match pair.key {
|
||||
"di" => self.filekinds.directory = pair.to_style(), // DIR
|
||||
"ex" => self.filekinds.executable = pair.to_style(), // EXEC
|
||||
"fi" => self.filekinds.normal = pair.to_style(), // FILE
|
||||
"pi" => self.filekinds.pipe = pair.to_style(), // FIFO
|
||||
"so" => self.filekinds.socket = pair.to_style(), // SOCK
|
||||
"bd" => self.filekinds.block_device = pair.to_style(), // BLK
|
||||
"cd" => self.filekinds.char_device = pair.to_style(), // CHR
|
||||
"ln" => self.filekinds.symlink = pair.to_style(), // LINK
|
||||
"or" => self.broken_symlink = pair.to_style(), // ORPHAN
|
||||
_ => return false,
|
||||
// Codes we don’t do anything with:
|
||||
// MULTIHARDLINK, DOOR, SETUID, SETGID, CAPABILITY,
|
||||
// STICKY_OTHER_WRITABLE, OTHER_WRITABLE, STICKY, MISSING
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Sets a value on this set of colours using one of the keys understood
|
||||
/// by the `EXA_COLORS` environment variable. Invalid keys set nothing,
|
||||
/// but return false. This doesn’t take the `LS_COLORS` keys into account,
|
||||
/// so `set_ls` should have been run first.
|
||||
pub fn set_exa(&mut self, pair: &Pair<'_>) -> bool {
|
||||
match pair.key {
|
||||
"ur" => self.perms.user_read = pair.to_style(),
|
||||
"uw" => self.perms.user_write = pair.to_style(),
|
||||
"ux" => self.perms.user_execute_file = pair.to_style(),
|
||||
"ue" => self.perms.user_execute_other = pair.to_style(),
|
||||
"gr" => self.perms.group_read = pair.to_style(),
|
||||
"gw" => self.perms.group_write = pair.to_style(),
|
||||
"gx" => self.perms.group_execute = pair.to_style(),
|
||||
"tr" => self.perms.other_read = pair.to_style(),
|
||||
"tw" => self.perms.other_write = pair.to_style(),
|
||||
"tx" => self.perms.other_execute = pair.to_style(),
|
||||
"su" => self.perms.special_user_file = pair.to_style(),
|
||||
"sf" => self.perms.special_other = pair.to_style(),
|
||||
"xa" => self.perms.attribute = pair.to_style(),
|
||||
|
||||
"sn" => self.set_number_style(pair.to_style()),
|
||||
"sb" => self.set_unit_style(pair.to_style()),
|
||||
"nb" => self.size.number_byte = pair.to_style(),
|
||||
"nk" => self.size.number_kilo = pair.to_style(),
|
||||
"nm" => self.size.number_mega = pair.to_style(),
|
||||
"ng" => self.size.number_giga = pair.to_style(),
|
||||
"nh" => self.size.number_huge = pair.to_style(),
|
||||
"ub" => self.size.unit_byte = pair.to_style(),
|
||||
"uk" => self.size.unit_kilo = pair.to_style(),
|
||||
"um" => self.size.unit_mega = pair.to_style(),
|
||||
"ug" => self.size.unit_giga = pair.to_style(),
|
||||
"uh" => self.size.unit_huge = pair.to_style(),
|
||||
"df" => self.size.major = pair.to_style(),
|
||||
"ds" => self.size.minor = pair.to_style(),
|
||||
|
||||
"uu" => self.users.user_you = pair.to_style(),
|
||||
"un" => self.users.user_someone_else = pair.to_style(),
|
||||
"gu" => self.users.group_yours = pair.to_style(),
|
||||
"gn" => self.users.group_not_yours = pair.to_style(),
|
||||
|
||||
"lc" => self.links.normal = pair.to_style(),
|
||||
"lm" => self.links.multi_link_file = pair.to_style(),
|
||||
|
||||
"ga" => self.git.new = pair.to_style(),
|
||||
"gm" => self.git.modified = pair.to_style(),
|
||||
"gd" => self.git.deleted = pair.to_style(),
|
||||
"gv" => self.git.renamed = pair.to_style(),
|
||||
"gt" => self.git.typechange = pair.to_style(),
|
||||
|
||||
"xx" => self.punctuation = pair.to_style(),
|
||||
"da" => self.date = pair.to_style(),
|
||||
"in" => self.inode = pair.to_style(),
|
||||
"bl" => self.blocks = pair.to_style(),
|
||||
"hd" => self.header = pair.to_style(),
|
||||
"lp" => self.symlink_path = pair.to_style(),
|
||||
"cc" => self.control_char = pair.to_style(),
|
||||
"bO" => self.broken_path_overlay = pair.to_style(),
|
||||
|
||||
_ => return false,
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn set_number_style(&mut self, style: Style) {
|
||||
self.size.number_byte = style;
|
||||
self.size.number_kilo = style;
|
||||
self.size.number_mega = style;
|
||||
self.size.number_giga = style;
|
||||
self.size.number_huge = style;
|
||||
}
|
||||
|
||||
pub fn set_unit_style(&mut self, style: Style) {
|
||||
self.size.unit_byte = style;
|
||||
self.size.unit_kilo = style;
|
||||
self.size.unit_mega = style;
|
||||
self.size.unit_giga = style;
|
||||
self.size.unit_huge = style;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl render::BlocksColours for Colours {
|
||||
fn block_count(&self) -> Style { self.blocks }
|
||||
fn no_blocks(&self) -> Style { self.punctuation }
|
||||
}
|
||||
|
||||
impl render::FiletypeColours for Colours {
|
||||
fn normal(&self) -> Style { self.filekinds.normal }
|
||||
fn directory(&self) -> Style { self.filekinds.directory }
|
||||
fn pipe(&self) -> Style { self.filekinds.pipe }
|
||||
fn symlink(&self) -> Style { self.filekinds.symlink }
|
||||
fn block_device(&self) -> Style { self.filekinds.block_device }
|
||||
fn char_device(&self) -> Style { self.filekinds.char_device }
|
||||
fn socket(&self) -> Style { self.filekinds.socket }
|
||||
fn special(&self) -> Style { self.filekinds.special }
|
||||
}
|
||||
|
||||
impl render::GitColours for Colours {
|
||||
fn not_modified(&self) -> Style { self.punctuation }
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
fn new(&self) -> Style { self.git.new }
|
||||
fn modified(&self) -> Style { self.git.modified }
|
||||
fn deleted(&self) -> Style { self.git.deleted }
|
||||
fn renamed(&self) -> Style { self.git.renamed }
|
||||
fn type_change(&self) -> Style { self.git.typechange }
|
||||
fn ignored(&self) -> Style { self.git.ignored }
|
||||
fn conflicted(&self) -> Style { self.git.conflicted }
|
||||
}
|
||||
|
||||
impl render::GroupColours for Colours {
|
||||
fn yours(&self) -> Style { self.users.group_yours }
|
||||
fn not_yours(&self) -> Style { self.users.group_not_yours }
|
||||
}
|
||||
|
||||
impl render::LinksColours for Colours {
|
||||
fn normal(&self) -> Style { self.links.normal }
|
||||
fn multi_link_file(&self) -> Style { self.links.multi_link_file }
|
||||
}
|
||||
|
||||
impl render::PermissionsColours for Colours {
|
||||
fn dash(&self) -> Style { self.punctuation }
|
||||
fn user_read(&self) -> Style { self.perms.user_read }
|
||||
fn user_write(&self) -> Style { self.perms.user_write }
|
||||
fn user_execute_file(&self) -> Style { self.perms.user_execute_file }
|
||||
fn user_execute_other(&self) -> Style { self.perms.user_execute_other }
|
||||
fn group_read(&self) -> Style { self.perms.group_read }
|
||||
fn group_write(&self) -> Style { self.perms.group_write }
|
||||
fn group_execute(&self) -> Style { self.perms.group_execute }
|
||||
fn other_read(&self) -> Style { self.perms.other_read }
|
||||
fn other_write(&self) -> Style { self.perms.other_write }
|
||||
fn other_execute(&self) -> Style { self.perms.other_execute }
|
||||
fn special_user_file(&self) -> Style { self.perms.special_user_file }
|
||||
fn special_other(&self) -> Style { self.perms.special_other }
|
||||
fn attribute(&self) -> Style { self.perms.attribute }
|
||||
}
|
||||
|
||||
impl render::SizeColours for Colours {
|
||||
fn size(&self, prefix: Option<number_prefix::Prefix>) -> Style {
|
||||
use number_prefix::Prefix::*;
|
||||
|
||||
match prefix {
|
||||
None => self.size.number_byte,
|
||||
Some(Kilo) | Some(Kibi) => self.size.number_kilo,
|
||||
Some(Mega) | Some(Mebi) => self.size.number_mega,
|
||||
Some(Giga) | Some(Gibi) => self.size.number_giga,
|
||||
Some(_) => self.size.number_huge,
|
||||
}
|
||||
}
|
||||
|
||||
fn unit(&self, prefix: Option<number_prefix::Prefix>) -> Style {
|
||||
use number_prefix::Prefix::*;
|
||||
|
||||
match prefix {
|
||||
None => self.size.unit_byte,
|
||||
Some(Kilo) | Some(Kibi) => self.size.unit_kilo,
|
||||
Some(Mega) | Some(Mebi) => self.size.unit_mega,
|
||||
Some(Giga) | Some(Gibi) => self.size.unit_giga,
|
||||
Some(_) => self.size.unit_huge,
|
||||
}
|
||||
}
|
||||
|
||||
fn no_size(&self) -> Style { self.punctuation }
|
||||
fn major(&self) -> Style { self.size.major }
|
||||
fn comma(&self) -> Style { self.punctuation }
|
||||
fn minor(&self) -> Style { self.size.minor }
|
||||
}
|
||||
|
||||
impl render::UserColours for Colours {
|
||||
fn you(&self) -> Style { self.users.user_you }
|
||||
fn someone_else(&self) -> Style { self.users.user_someone_else }
|
||||
}
|
||||
|
||||
impl FileNameColours for Colours {
|
||||
fn normal_arrow(&self) -> Style { self.punctuation }
|
||||
fn broken_symlink(&self) -> Style { self.broken_symlink }
|
||||
fn broken_filename(&self) -> Style { apply_overlay(self.broken_symlink, self.broken_path_overlay) }
|
||||
fn broken_control_char(&self) -> Style { apply_overlay(self.control_char, self.broken_path_overlay) }
|
||||
fn control_char(&self) -> Style { self.control_char }
|
||||
fn symlink_path(&self) -> Style { self.symlink_path }
|
||||
fn executable_file(&self) -> Style { self.filekinds.executable }
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
mod colours;
|
||||
pub use self::colours::Colours;
|
||||
pub use self::colours::Size as SizeColours;
|
||||
|
||||
mod lsc;
|
||||
pub use self::lsc::LSColors;
|
130
src/theme/default_theme.rs
Normal file
130
src/theme/default_theme.rs
Normal file
@ -0,0 +1,130 @@
|
||||
use ansi_term::Style;
|
||||
use ansi_term::Colour::*;
|
||||
|
||||
use crate::theme::ColourScale;
|
||||
use crate::theme::ui_styles::*;
|
||||
|
||||
|
||||
impl UiStyles {
|
||||
pub fn default_theme(scale: ColourScale) -> Self {
|
||||
Self {
|
||||
colourful: true,
|
||||
|
||||
filekinds: FileKinds {
|
||||
normal: Style::default(),
|
||||
directory: Blue.bold(),
|
||||
symlink: Cyan.normal(),
|
||||
pipe: Yellow.normal(),
|
||||
block_device: Yellow.bold(),
|
||||
char_device: Yellow.bold(),
|
||||
socket: Red.bold(),
|
||||
special: Yellow.normal(),
|
||||
executable: Green.bold(),
|
||||
},
|
||||
|
||||
perms: Permissions {
|
||||
user_read: Yellow.bold(),
|
||||
user_write: Red.bold(),
|
||||
user_execute_file: Green.bold().underline(),
|
||||
user_execute_other: Green.bold(),
|
||||
|
||||
group_read: Yellow.normal(),
|
||||
group_write: Red.normal(),
|
||||
group_execute: Green.normal(),
|
||||
|
||||
other_read: Yellow.normal(),
|
||||
other_write: Red.normal(),
|
||||
other_execute: Green.normal(),
|
||||
|
||||
special_user_file: Purple.normal(),
|
||||
special_other: Purple.normal(),
|
||||
|
||||
attribute: Style::default(),
|
||||
},
|
||||
|
||||
size: Size::colourful(scale),
|
||||
|
||||
users: Users {
|
||||
user_you: Yellow.bold(),
|
||||
user_someone_else: Style::default(),
|
||||
group_yours: Yellow.bold(),
|
||||
group_not_yours: Style::default(),
|
||||
},
|
||||
|
||||
links: Links {
|
||||
normal: Red.bold(),
|
||||
multi_link_file: Red.on(Yellow),
|
||||
},
|
||||
|
||||
git: Git {
|
||||
new: Green.normal(),
|
||||
modified: Blue.normal(),
|
||||
deleted: Red.normal(),
|
||||
renamed: Yellow.normal(),
|
||||
typechange: Purple.normal(),
|
||||
ignored: Style::default().dimmed(),
|
||||
conflicted: Red.normal(),
|
||||
},
|
||||
|
||||
punctuation: Fixed(244).normal(),
|
||||
date: Blue.normal(),
|
||||
inode: Purple.normal(),
|
||||
blocks: Cyan.normal(),
|
||||
octal: Purple.normal(),
|
||||
header: Style::default().underline(),
|
||||
|
||||
symlink_path: Cyan.normal(),
|
||||
control_char: Red.normal(),
|
||||
broken_symlink: Red.normal(),
|
||||
broken_path_overlay: Style::default().underline(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Size {
|
||||
pub fn colourful(scale: ColourScale) -> Self {
|
||||
match scale {
|
||||
ColourScale::Gradient => Self::colourful_gradient(),
|
||||
ColourScale::Fixed => Self::colourful_fixed(),
|
||||
}
|
||||
}
|
||||
|
||||
fn colourful_fixed() -> Self {
|
||||
Self {
|
||||
major: Green.bold(),
|
||||
minor: Green.normal(),
|
||||
|
||||
number_byte: Green.bold(),
|
||||
number_kilo: Green.bold(),
|
||||
number_mega: Green.bold(),
|
||||
number_giga: Green.bold(),
|
||||
number_huge: Green.bold(),
|
||||
|
||||
unit_byte: Green.normal(),
|
||||
unit_kilo: Green.normal(),
|
||||
unit_mega: Green.normal(),
|
||||
unit_giga: Green.normal(),
|
||||
unit_huge: Green.normal(),
|
||||
}
|
||||
}
|
||||
|
||||
fn colourful_gradient() -> Self {
|
||||
Self {
|
||||
major: Green.bold(),
|
||||
minor: Green.normal(),
|
||||
|
||||
number_byte: Fixed(118).normal(),
|
||||
number_kilo: Fixed(190).normal(),
|
||||
number_mega: Fixed(226).normal(),
|
||||
number_giga: Fixed(220).normal(),
|
||||
number_huge: Fixed(214).normal(),
|
||||
|
||||
unit_byte: Green.normal(),
|
||||
unit_kilo: Green.normal(),
|
||||
unit_mega: Green.normal(),
|
||||
unit_giga: Green.normal(),
|
||||
unit_huge: Green.normal(),
|
||||
}
|
||||
}
|
||||
}
|
@ -70,8 +70,9 @@ where I: Iterator<Item = &'a str>
|
||||
}*/
|
||||
|
||||
if let (Some(r), Some(g), Some(b)) = (hexes.parse().ok(),
|
||||
iter.next().and_then(|s| s.parse().ok()),
|
||||
iter.next().and_then(|s| s.parse().ok())) {
|
||||
iter.next().and_then(|s| s.parse().ok()),
|
||||
iter.next().and_then(|s| s.parse().ok()))
|
||||
{
|
||||
return Some(RGB(r, g, b));
|
||||
}
|
||||
}
|
@ -1,11 +1,28 @@
|
||||
use ansi_term::Style;
|
||||
|
||||
use crate::fs::File;
|
||||
use crate::options::{flags, Vars, OptionsError};
|
||||
use crate::options::parser::MatchedFlags;
|
||||
use crate::output::file_name::{Classify, FileStyle};
|
||||
use crate::style::Colours;
|
||||
use crate::output::file_name::Colours as FileNameColours;
|
||||
use crate::output::render;
|
||||
|
||||
mod ui_styles;
|
||||
pub use self::ui_styles::UiStyles;
|
||||
pub use self::ui_styles::Size as SizeColours;
|
||||
|
||||
mod lsc;
|
||||
pub use self::lsc::LSColors;
|
||||
|
||||
mod default_theme;
|
||||
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub struct Options {
|
||||
|
||||
pub use_colours: UseColours,
|
||||
|
||||
pub colour_scale: ColourScale,
|
||||
|
||||
pub definitions: Definitions,
|
||||
}
|
||||
|
||||
/// Under what circumstances we should display coloured, rather than plain,
|
||||
/// output to the terminal.
|
||||
@ -14,8 +31,8 @@ use crate::style::Colours;
|
||||
/// Turning them on when output is going to, say, a pipe, would make programs
|
||||
/// such as `grep` or `more` not work properly. So the `Automatic` mode does
|
||||
/// this check and only displays colours when they can be truly appreciated.
|
||||
#[derive(PartialEq, Debug)]
|
||||
enum TerminalColours {
|
||||
#[derive(PartialEq, Debug, Copy, Clone)]
|
||||
pub enum UseColours {
|
||||
|
||||
/// Display them even when output isn’t going to a terminal.
|
||||
Always,
|
||||
@ -27,81 +44,39 @@ enum TerminalColours {
|
||||
Never,
|
||||
}
|
||||
|
||||
impl Default for TerminalColours {
|
||||
fn default() -> Self {
|
||||
Self::Automatic
|
||||
}
|
||||
#[derive(PartialEq, Debug, Copy, Clone)]
|
||||
pub enum ColourScale {
|
||||
Fixed,
|
||||
Gradient,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Default)]
|
||||
pub struct Definitions {
|
||||
pub ls: Option<String>,
|
||||
pub exa: Option<String>,
|
||||
}
|
||||
|
||||
|
||||
impl TerminalColours {
|
||||
|
||||
/// Determine which terminal colour conditions to use.
|
||||
fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
|
||||
let word = match matches.get_where(|f| f.matches(&flags::COLOR) || f.matches(&flags::COLOUR))? {
|
||||
Some(w) => w,
|
||||
None => return Ok(Self::default()),
|
||||
};
|
||||
|
||||
if word == "always" {
|
||||
Ok(Self::Always)
|
||||
}
|
||||
else if word == "auto" || word == "automatic" {
|
||||
Ok(Self::Automatic)
|
||||
}
|
||||
else if word == "never" {
|
||||
Ok(Self::Never)
|
||||
}
|
||||
else {
|
||||
Err(OptionsError::BadArgument(&flags::COLOR, word.into()))
|
||||
}
|
||||
}
|
||||
pub struct Theme {
|
||||
pub ui: UiStyles,
|
||||
pub exts: Box<dyn FileColours>,
|
||||
}
|
||||
|
||||
|
||||
/// **Styles**, which is already an overloaded term, is a pair of view option
|
||||
/// sets that happen to both be affected by `LS_COLORS` and `EXA_COLORS`.
|
||||
/// Because it’s better to only iterate through that once, the two are deduced
|
||||
/// together.
|
||||
pub struct Styles {
|
||||
|
||||
/// The colours to paint user interface elements, like the date column,
|
||||
/// and file kinds, such as directories.
|
||||
pub colours: Colours,
|
||||
|
||||
/// The colours to paint the names of files that match glob patterns
|
||||
/// (and the classify option).
|
||||
pub style: FileStyle,
|
||||
}
|
||||
|
||||
impl Styles {
|
||||
impl Options {
|
||||
|
||||
#[allow(trivial_casts)] // the `as Box<_>` stuff below warns about this for some reason
|
||||
pub fn deduce<V, TW>(matches: &MatchedFlags<'_>, vars: &V, widther: TW) -> Result<Self, OptionsError>
|
||||
where TW: Fn() -> Option<usize>, V: Vars {
|
||||
pub fn to_theme(&self, isatty: bool) -> Theme {
|
||||
use crate::info::filetype::FileExtensions;
|
||||
use crate::output::file_name::NoFileColours;
|
||||
|
||||
let classify = Classify::deduce(matches)?;
|
||||
|
||||
// Before we do anything else, figure out if we need to consider
|
||||
// custom colours at all
|
||||
let tc = TerminalColours::deduce(matches)?;
|
||||
|
||||
if tc == TerminalColours::Never || (tc == TerminalColours::Automatic && widther().is_none()) {
|
||||
if self.use_colours == UseColours::Never || (self.use_colours == UseColours::Automatic && ! isatty) {
|
||||
let ui = UiStyles::plain();
|
||||
let exts = Box::new(NoFileColours);
|
||||
|
||||
return Ok(Self {
|
||||
colours: Colours::plain(),
|
||||
style: FileStyle { classify, exts },
|
||||
});
|
||||
return Theme { ui, exts };
|
||||
}
|
||||
|
||||
// Parse the environment variables into colours and extension mappings
|
||||
let scale = matches.has_where(|f| f.matches(&flags::COLOR_SCALE) || f.matches(&flags::COLOUR_SCALE))?;
|
||||
let mut colours = Colours::colourful(scale.is_some());
|
||||
|
||||
let (exts, use_default_filetypes) = parse_color_vars(vars, &mut colours);
|
||||
let mut ui = UiStyles::default_theme(self.colour_scale);
|
||||
let (exts, use_default_filetypes) = self.definitions.parse_color_vars(&mut ui);
|
||||
|
||||
// Use between 0 and 2 file name highlighters
|
||||
let exts = match (exts.is_non_empty(), use_default_filetypes) {
|
||||
@ -111,67 +86,89 @@ impl Styles {
|
||||
( true, true) => Box::new((exts, FileExtensions)) as Box<_>,
|
||||
};
|
||||
|
||||
let style = FileStyle { classify, exts };
|
||||
Ok(Self { colours, style })
|
||||
Theme { ui, exts }
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse the environment variables into `LS_COLORS` pairs, putting file glob
|
||||
/// colours into the `ExtensionMappings` that gets returned, and using the
|
||||
/// two-character UI codes to modify the mutable `Colours`.
|
||||
///
|
||||
/// Also returns if the `EXA_COLORS` variable should reset the existing file
|
||||
/// type mappings or not. The `reset` code needs to be the first one.
|
||||
fn parse_color_vars<V: Vars>(vars: &V, colours: &mut Colours) -> (ExtensionMappings, bool) {
|
||||
use log::*;
|
||||
impl Definitions {
|
||||
|
||||
use crate::options::vars;
|
||||
use crate::style::LSColors;
|
||||
/// Parse the environment variables into `LS_COLORS` pairs, putting file glob
|
||||
/// colours into the `ExtensionMappings` that gets returned, and using the
|
||||
/// two-character UI codes to modify the mutable `Colours`.
|
||||
///
|
||||
/// Also returns if the `EXA_COLORS` variable should reset the existing file
|
||||
/// type mappings or not. The `reset` code needs to be the first one.
|
||||
fn parse_color_vars(&self, colours: &mut UiStyles) -> (ExtensionMappings, bool) {
|
||||
use log::*;
|
||||
|
||||
let mut exts = ExtensionMappings::default();
|
||||
let mut exts = ExtensionMappings::default();
|
||||
|
||||
if let Some(lsc) = vars.get(vars::LS_COLORS) {
|
||||
let lsc = lsc.to_string_lossy();
|
||||
|
||||
LSColors(lsc.as_ref()).each_pair(|pair| {
|
||||
if ! colours.set_ls(&pair) {
|
||||
match glob::Pattern::new(pair.key) {
|
||||
Ok(pat) => {
|
||||
exts.add(pat, pair.to_style());
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Couldn't parse glob pattern {:?}: {}", pair.key, e);
|
||||
if let Some(lsc) = &self.ls {
|
||||
LSColors(lsc).each_pair(|pair| {
|
||||
if ! colours.set_ls(&pair) {
|
||||
match glob::Pattern::new(pair.key) {
|
||||
Ok(pat) => {
|
||||
exts.add(pat, pair.to_style());
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Couldn't parse glob pattern {:?}: {}", pair.key, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let mut use_default_filetypes = true;
|
||||
|
||||
if let Some(exa) = vars.get(vars::EXA_COLORS) {
|
||||
let exa = exa.to_string_lossy();
|
||||
|
||||
// Is this hacky? Yes.
|
||||
if exa == "reset" || exa.starts_with("reset:") {
|
||||
use_default_filetypes = false;
|
||||
});
|
||||
}
|
||||
|
||||
LSColors(exa.as_ref()).each_pair(|pair| {
|
||||
if ! colours.set_ls(&pair) && ! colours.set_exa(&pair) {
|
||||
match glob::Pattern::new(pair.key) {
|
||||
Ok(pat) => {
|
||||
exts.add(pat, pair.to_style());
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Couldn't parse glob pattern {:?}: {}", pair.key, e);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
let mut use_default_filetypes = true;
|
||||
|
||||
(exts, use_default_filetypes)
|
||||
if let Some(exa) = &self.exa {
|
||||
// Is this hacky? Yes.
|
||||
if exa == "reset" || exa.starts_with("reset:") {
|
||||
use_default_filetypes = false;
|
||||
}
|
||||
|
||||
LSColors(exa).each_pair(|pair| {
|
||||
if ! colours.set_ls(&pair) && ! colours.set_exa(&pair) {
|
||||
match glob::Pattern::new(pair.key) {
|
||||
Ok(pat) => {
|
||||
exts.add(pat, pair.to_style());
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Couldn't parse glob pattern {:?}: {}", pair.key, e);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
(exts, use_default_filetypes)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub trait FileColours: std::marker::Sync {
|
||||
fn colour_file(&self, file: &File<'_>) -> Option<Style>;
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
struct NoFileColours;
|
||||
impl FileColours for NoFileColours {
|
||||
fn colour_file(&self, _file: &File<'_>) -> Option<Style> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// When getting the colour of a file from a *pair* of colourisers, try the
|
||||
// first one then try the second one. This lets the user provide their own
|
||||
// file type associations, while falling back to the default set if not set
|
||||
// explicitly.
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -183,7 +180,6 @@ struct ExtensionMappings {
|
||||
// Loop through backwards so that colours specified later in the list override
|
||||
// colours specified earlier, like we do with options and strict mode
|
||||
|
||||
use crate::output::file_name::FileColours;
|
||||
impl FileColours for ExtensionMappings {
|
||||
fn colour_file(&self, file: &File<'_>) -> Option<Style> {
|
||||
self.mappings.iter().rev()
|
||||
@ -203,166 +199,164 @@ impl ExtensionMappings {
|
||||
}
|
||||
|
||||
|
||||
impl Classify {
|
||||
fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
|
||||
let flagged = matches.has(&flags::CLASSIFY)?;
|
||||
|
||||
if flagged { Ok(Self::AddFileIndicators) }
|
||||
else { Ok(Self::JustFilenames) }
|
||||
|
||||
impl render::BlocksColours for Theme {
|
||||
fn block_count(&self) -> Style { self.ui.blocks }
|
||||
fn no_blocks(&self) -> Style { self.ui.punctuation }
|
||||
}
|
||||
|
||||
impl render::FiletypeColours for Theme {
|
||||
fn normal(&self) -> Style { self.ui.filekinds.normal }
|
||||
fn directory(&self) -> Style { self.ui.filekinds.directory }
|
||||
fn pipe(&self) -> Style { self.ui.filekinds.pipe }
|
||||
fn symlink(&self) -> Style { self.ui.filekinds.symlink }
|
||||
fn block_device(&self) -> Style { self.ui.filekinds.block_device }
|
||||
fn char_device(&self) -> Style { self.ui.filekinds.char_device }
|
||||
fn socket(&self) -> Style { self.ui.filekinds.socket }
|
||||
fn special(&self) -> Style { self.ui.filekinds.special }
|
||||
}
|
||||
|
||||
impl render::GitColours for Theme {
|
||||
fn not_modified(&self) -> Style { self.ui.punctuation }
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
fn new(&self) -> Style { self.ui.git.new }
|
||||
fn modified(&self) -> Style { self.ui.git.modified }
|
||||
fn deleted(&self) -> Style { self.ui.git.deleted }
|
||||
fn renamed(&self) -> Style { self.ui.git.renamed }
|
||||
fn type_change(&self) -> Style { self.ui.git.typechange }
|
||||
fn ignored(&self) -> Style { self.ui.git.ignored }
|
||||
fn conflicted(&self) -> Style { self.ui.git.conflicted }
|
||||
}
|
||||
|
||||
impl render::GroupColours for Theme {
|
||||
fn yours(&self) -> Style { self.ui.users.group_yours }
|
||||
fn not_yours(&self) -> Style { self.ui.users.group_not_yours }
|
||||
}
|
||||
|
||||
impl render::LinksColours for Theme {
|
||||
fn normal(&self) -> Style { self.ui.links.normal }
|
||||
fn multi_link_file(&self) -> Style { self.ui.links.multi_link_file }
|
||||
}
|
||||
|
||||
impl render::PermissionsColours for Theme {
|
||||
fn dash(&self) -> Style { self.ui.punctuation }
|
||||
fn user_read(&self) -> Style { self.ui.perms.user_read }
|
||||
fn user_write(&self) -> Style { self.ui.perms.user_write }
|
||||
fn user_execute_file(&self) -> Style { self.ui.perms.user_execute_file }
|
||||
fn user_execute_other(&self) -> Style { self.ui.perms.user_execute_other }
|
||||
fn group_read(&self) -> Style { self.ui.perms.group_read }
|
||||
fn group_write(&self) -> Style { self.ui.perms.group_write }
|
||||
fn group_execute(&self) -> Style { self.ui.perms.group_execute }
|
||||
fn other_read(&self) -> Style { self.ui.perms.other_read }
|
||||
fn other_write(&self) -> Style { self.ui.perms.other_write }
|
||||
fn other_execute(&self) -> Style { self.ui.perms.other_execute }
|
||||
fn special_user_file(&self) -> Style { self.ui.perms.special_user_file }
|
||||
fn special_other(&self) -> Style { self.ui.perms.special_other }
|
||||
fn attribute(&self) -> Style { self.ui.perms.attribute }
|
||||
}
|
||||
|
||||
impl render::SizeColours for Theme {
|
||||
fn size(&self, prefix: Option<number_prefix::Prefix>) -> Style {
|
||||
use number_prefix::Prefix::*;
|
||||
|
||||
match prefix {
|
||||
None => self.ui.size.number_byte,
|
||||
Some(Kilo) | Some(Kibi) => self.ui.size.number_kilo,
|
||||
Some(Mega) | Some(Mebi) => self.ui.size.number_mega,
|
||||
Some(Giga) | Some(Gibi) => self.ui.size.number_giga,
|
||||
Some(_) => self.ui.size.number_huge,
|
||||
}
|
||||
}
|
||||
|
||||
fn unit(&self, prefix: Option<number_prefix::Prefix>) -> Style {
|
||||
use number_prefix::Prefix::*;
|
||||
|
||||
match prefix {
|
||||
None => self.ui.size.unit_byte,
|
||||
Some(Kilo) | Some(Kibi) => self.ui.size.unit_kilo,
|
||||
Some(Mega) | Some(Mebi) => self.ui.size.unit_mega,
|
||||
Some(Giga) | Some(Gibi) => self.ui.size.unit_giga,
|
||||
Some(_) => self.ui.size.unit_huge,
|
||||
}
|
||||
}
|
||||
|
||||
fn no_size(&self) -> Style { self.ui.punctuation }
|
||||
fn major(&self) -> Style { self.ui.size.major }
|
||||
fn comma(&self) -> Style { self.ui.punctuation }
|
||||
fn minor(&self) -> Style { self.ui.size.minor }
|
||||
}
|
||||
|
||||
impl render::UserColours for Theme {
|
||||
fn you(&self) -> Style { self.ui.users.user_you }
|
||||
fn someone_else(&self) -> Style { self.ui.users.user_someone_else }
|
||||
}
|
||||
|
||||
impl FileNameColours for Theme {
|
||||
fn normal_arrow(&self) -> Style { self.ui.punctuation }
|
||||
fn broken_symlink(&self) -> Style { self.ui.broken_symlink }
|
||||
fn broken_filename(&self) -> Style { apply_overlay(self.ui.broken_symlink, self.ui.broken_path_overlay) }
|
||||
fn broken_control_char(&self) -> Style { apply_overlay(self.ui.control_char, self.ui.broken_path_overlay) }
|
||||
fn control_char(&self) -> Style { self.ui.control_char }
|
||||
fn symlink_path(&self) -> Style { self.ui.symlink_path }
|
||||
fn executable_file(&self) -> Style { self.ui.filekinds.executable }
|
||||
|
||||
fn colour_file(&self, file: &File<'_>) -> Style {
|
||||
self.exts.colour_file(file).unwrap_or(self.ui.filekinds.normal)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod terminal_test {
|
||||
use super::*;
|
||||
use std::ffi::OsString;
|
||||
use crate::options::flags;
|
||||
use crate::options::parser::{Flag, Arg};
|
||||
/// Some of the styles are **overlays**: although they have the same attribute
|
||||
/// set as regular styles (foreground and background colours, bold, underline,
|
||||
/// etc), they’re intended to be used to *amend* existing styles.
|
||||
///
|
||||
/// For example, the target path of a broken symlink is displayed in a red,
|
||||
/// underlined style by default. Paths can contain control characters, so
|
||||
/// these control characters need to be underlined too, otherwise it looks
|
||||
/// weird. So instead of having four separate configurable styles for “link
|
||||
/// path”, “broken link path”, “control character” and “broken control
|
||||
/// character”, there are styles for “link path”, “control character”, and
|
||||
/// “broken link overlay”, the latter of which is just set to override the
|
||||
/// underline attribute on the other two.
|
||||
fn apply_overlay(mut base: Style, overlay: Style) -> Style {
|
||||
if let Some(fg) = overlay.foreground { base.foreground = Some(fg); }
|
||||
if let Some(bg) = overlay.background { base.background = Some(bg); }
|
||||
|
||||
use crate::options::test::parse_for_test;
|
||||
use crate::options::test::Strictnesses::*;
|
||||
if overlay.is_bold { base.is_bold = true; }
|
||||
if overlay.is_dimmed { base.is_dimmed = true; }
|
||||
if overlay.is_italic { base.is_italic = true; }
|
||||
if overlay.is_underline { base.is_underline = true; }
|
||||
if overlay.is_blink { base.is_blink = true; }
|
||||
if overlay.is_reverse { base.is_reverse = true; }
|
||||
if overlay.is_hidden { base.is_hidden = true; }
|
||||
if overlay.is_strikethrough { base.is_strikethrough = true; }
|
||||
|
||||
static TEST_ARGS: &[&Arg] = &[ &flags::COLOR, &flags::COLOUR ];
|
||||
|
||||
macro_rules! test {
|
||||
($name:ident: $inputs:expr; $stricts:expr => $result:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| TerminalColours::deduce(mf)) {
|
||||
assert_eq!(result, $result);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
($name:ident: $inputs:expr; $stricts:expr => err $result:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| TerminalColours::deduce(mf)) {
|
||||
assert_eq!(result.unwrap_err(), $result);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Default
|
||||
test!(empty: []; Both => Ok(TerminalColours::default()));
|
||||
|
||||
// --colour
|
||||
test!(u_always: ["--colour=always"]; Both => Ok(TerminalColours::Always));
|
||||
test!(u_auto: ["--colour", "auto"]; Both => Ok(TerminalColours::Automatic));
|
||||
test!(u_never: ["--colour=never"]; Both => Ok(TerminalColours::Never));
|
||||
|
||||
// --color
|
||||
test!(no_u_always: ["--color", "always"]; Both => Ok(TerminalColours::Always));
|
||||
test!(no_u_auto: ["--color=auto"]; Both => Ok(TerminalColours::Automatic));
|
||||
test!(no_u_never: ["--color", "never"]; Both => Ok(TerminalColours::Never));
|
||||
|
||||
// Errors
|
||||
test!(no_u_error: ["--color=upstream"]; Both => err OptionsError::BadArgument(&flags::COLOR, OsString::from("upstream"))); // the error is for --color
|
||||
test!(u_error: ["--colour=lovers"]; Both => err OptionsError::BadArgument(&flags::COLOR, OsString::from("lovers"))); // and so is this one!
|
||||
|
||||
// Overriding
|
||||
test!(overridden_1: ["--colour=auto", "--colour=never"]; Last => Ok(TerminalColours::Never));
|
||||
test!(overridden_2: ["--color=auto", "--colour=never"]; Last => Ok(TerminalColours::Never));
|
||||
test!(overridden_3: ["--colour=auto", "--color=never"]; Last => Ok(TerminalColours::Never));
|
||||
test!(overridden_4: ["--color=auto", "--color=never"]; Last => Ok(TerminalColours::Never));
|
||||
|
||||
test!(overridden_5: ["--colour=auto", "--colour=never"]; Complain => err OptionsError::Duplicate(Flag::Long("colour"), Flag::Long("colour")));
|
||||
test!(overridden_6: ["--color=auto", "--colour=never"]; Complain => err OptionsError::Duplicate(Flag::Long("color"), Flag::Long("colour")));
|
||||
test!(overridden_7: ["--colour=auto", "--color=never"]; Complain => err OptionsError::Duplicate(Flag::Long("colour"), Flag::Long("color")));
|
||||
test!(overridden_8: ["--color=auto", "--color=never"]; Complain => err OptionsError::Duplicate(Flag::Long("color"), Flag::Long("color")));
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod colour_test {
|
||||
use super::*;
|
||||
use crate::options::flags;
|
||||
use crate::options::parser::{Flag, Arg};
|
||||
|
||||
use crate::options::test::parse_for_test;
|
||||
use crate::options::test::Strictnesses::*;
|
||||
|
||||
static TEST_ARGS: &[&Arg] = &[ &flags::COLOR, &flags::COLOUR,
|
||||
&flags::COLOR_SCALE, &flags::COLOUR_SCALE ];
|
||||
|
||||
macro_rules! test {
|
||||
($name:ident: $inputs:expr, $widther:expr; $stricts:expr => $result:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| Styles::deduce(mf, &None, &$widther).map(|s| s.colours)) {
|
||||
assert_eq!(result, $result);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
($name:ident: $inputs:expr, $widther:expr; $stricts:expr => err $result:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| Styles::deduce(mf, &None, &$widther).map(|s| s.colours)) {
|
||||
assert_eq!(result.unwrap_err(), $result);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
($name:ident: $inputs:expr, $widther:expr; $stricts:expr => like $pat:pat) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| Styles::deduce(mf, &None, &$widther).map(|s| s.colours)) {
|
||||
println!("Testing {:?}", result);
|
||||
match result {
|
||||
$pat => assert!(true),
|
||||
_ => assert!(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test!(width_1: ["--colour", "always"], || Some(80); Both => Ok(Colours::colourful(false)));
|
||||
test!(width_2: ["--colour", "always"], || None; Both => Ok(Colours::colourful(false)));
|
||||
test!(width_3: ["--colour", "never"], || Some(80); Both => Ok(Colours::plain()));
|
||||
test!(width_4: ["--colour", "never"], || None; Both => Ok(Colours::plain()));
|
||||
test!(width_5: ["--colour", "automatic"], || Some(80); Both => Ok(Colours::colourful(false)));
|
||||
test!(width_6: ["--colour", "automatic"], || None; Both => Ok(Colours::plain()));
|
||||
test!(width_7: [], || Some(80); Both => Ok(Colours::colourful(false)));
|
||||
test!(width_8: [], || None; Both => Ok(Colours::plain()));
|
||||
|
||||
test!(scale_1: ["--color=always", "--color-scale", "--colour-scale"], || None; Last => Ok(Colours::colourful(true)));
|
||||
test!(scale_2: ["--color=always", "--color-scale", ], || None; Last => Ok(Colours::colourful(true)));
|
||||
test!(scale_3: ["--color=always", "--colour-scale"], || None; Last => Ok(Colours::colourful(true)));
|
||||
test!(scale_4: ["--color=always", ], || None; Last => Ok(Colours::colourful(false)));
|
||||
|
||||
test!(scale_5: ["--color=always", "--color-scale", "--colour-scale"], || None; Complain => err OptionsError::Duplicate(Flag::Long("color-scale"), Flag::Long("colour-scale")));
|
||||
test!(scale_6: ["--color=always", "--color-scale", ], || None; Complain => Ok(Colours::colourful(true)));
|
||||
test!(scale_7: ["--color=always", "--colour-scale"], || None; Complain => Ok(Colours::colourful(true)));
|
||||
test!(scale_8: ["--color=always", ], || None; Complain => Ok(Colours::colourful(false)));
|
||||
base
|
||||
}
|
||||
// TODO: move this function to the ansi_term crate
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod customs_test {
|
||||
use std::ffi::OsString;
|
||||
|
||||
use super::*;
|
||||
use crate::options::Vars;
|
||||
|
||||
use crate::theme::ui_styles::UiStyles;
|
||||
use ansi_term::Colour::*;
|
||||
|
||||
macro_rules! test {
|
||||
($name:ident: ls $ls:expr, exa $exa:expr => colours $expected:ident -> $process_expected:expr) => {
|
||||
#[test]
|
||||
#[allow(unused_mut)]
|
||||
fn $name() {
|
||||
let mut $expected = Colours::colourful(false);
|
||||
let mut $expected = UiStyles::default();
|
||||
$process_expected();
|
||||
|
||||
let vars = MockVars { ls: $ls, exa: $exa };
|
||||
let definitions = Definitions {
|
||||
ls: Some($ls.into()),
|
||||
exa: Some($exa.into()),
|
||||
};
|
||||
|
||||
let mut result = Colours::colourful(false);
|
||||
let (_exts, _reset) = parse_color_vars(&vars, &mut result);
|
||||
let mut result = UiStyles::default();
|
||||
let (_exts, _reset) = definitions.parse_color_vars(&mut result);
|
||||
assert_eq!($expected, result);
|
||||
}
|
||||
};
|
||||
@ -374,18 +368,19 @@ mod customs_test {
|
||||
.map(|t| (glob::Pattern::new(t.0).unwrap(), t.1))
|
||||
.collect();
|
||||
|
||||
let vars = MockVars { ls: $ls, exa: $exa };
|
||||
let definitions = Definitions {
|
||||
ls: Some($ls.into()),
|
||||
exa: Some($exa.into()),
|
||||
};
|
||||
|
||||
let mut meh = Colours::colourful(false);
|
||||
let (result, _reset) = parse_color_vars(&vars, &mut meh);
|
||||
let (result, _reset) = definitions.parse_color_vars(&mut UiStyles::default());
|
||||
assert_eq!(ExtensionMappings { mappings }, result);
|
||||
}
|
||||
};
|
||||
($name:ident: ls $ls:expr, exa $exa:expr => colours $expected:ident -> $process_expected:expr, exts $mappings:expr) => {
|
||||
#[test]
|
||||
#[allow(unused_mut)]
|
||||
fn $name() {
|
||||
let mut $expected = Colours::colourful(false);
|
||||
let mut $expected = UiStyles::colourful(false);
|
||||
$process_expected();
|
||||
|
||||
let mappings: Vec<(glob::Pattern, Style)>
|
||||
@ -393,37 +388,19 @@ mod customs_test {
|
||||
.map(|t| (glob::Pattern::new(t.0).unwrap(), t.1))
|
||||
.collect();
|
||||
|
||||
let vars = MockVars { ls: $ls, exa: $exa };
|
||||
let definitions = Definitions {
|
||||
ls: Some($ls.into()),
|
||||
exa: Some($exa.into()),
|
||||
};
|
||||
|
||||
let mut meh = Colours::colourful(false);
|
||||
let (result, _reset) = parse_color_vars(&vars, &mut meh);
|
||||
let mut meh = UiStyles::colourful(false);
|
||||
let (result, _reset) = definitions.parse_color_vars(&vars, &mut meh);
|
||||
assert_eq!(ExtensionMappings { mappings }, result);
|
||||
assert_eq!($expected, meh);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
struct MockVars {
|
||||
ls: &'static str,
|
||||
exa: &'static str,
|
||||
}
|
||||
|
||||
// Test impl that just returns the value it has.
|
||||
impl Vars for MockVars {
|
||||
fn get(&self, name: &'static str) -> Option<OsString> {
|
||||
use crate::options::vars;
|
||||
|
||||
if name == vars::LS_COLORS && ! self.ls.is_empty() {
|
||||
Some(OsString::from(self.ls.clone()))
|
||||
}
|
||||
else if name == vars::EXA_COLORS && ! self.exa.is_empty() {
|
||||
Some(OsString::from(self.exa.clone()))
|
||||
}
|
||||
else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// LS_COLORS can affect all of these colours:
|
||||
test!(ls_di: ls "di=31", exa "" => colours c -> { c.filekinds.directory = Red.normal(); });
|
217
src/theme/ui_styles.rs
Normal file
217
src/theme/ui_styles.rs
Normal file
@ -0,0 +1,217 @@
|
||||
use ansi_term::Style;
|
||||
|
||||
use crate::theme::lsc::Pair;
|
||||
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub struct UiStyles {
|
||||
pub colourful: bool,
|
||||
|
||||
pub filekinds: FileKinds,
|
||||
pub perms: Permissions,
|
||||
pub size: Size,
|
||||
pub users: Users,
|
||||
pub links: Links,
|
||||
pub git: Git,
|
||||
|
||||
pub punctuation: Style,
|
||||
pub date: Style,
|
||||
pub inode: Style,
|
||||
pub blocks: Style,
|
||||
pub header: Style,
|
||||
pub octal: Style,
|
||||
|
||||
pub symlink_path: Style,
|
||||
pub control_char: Style,
|
||||
pub broken_symlink: Style,
|
||||
pub broken_path_overlay: Style,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub struct FileKinds {
|
||||
pub normal: Style,
|
||||
pub directory: Style,
|
||||
pub symlink: Style,
|
||||
pub pipe: Style,
|
||||
pub block_device: Style,
|
||||
pub char_device: Style,
|
||||
pub socket: Style,
|
||||
pub special: Style,
|
||||
pub executable: Style,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub struct Permissions {
|
||||
pub user_read: Style,
|
||||
pub user_write: Style,
|
||||
pub user_execute_file: Style,
|
||||
pub user_execute_other: Style,
|
||||
|
||||
pub group_read: Style,
|
||||
pub group_write: Style,
|
||||
pub group_execute: Style,
|
||||
|
||||
pub other_read: Style,
|
||||
pub other_write: Style,
|
||||
pub other_execute: Style,
|
||||
|
||||
pub special_user_file: Style,
|
||||
pub special_other: Style,
|
||||
|
||||
pub attribute: Style,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub struct Size {
|
||||
pub major: Style,
|
||||
pub minor: Style,
|
||||
|
||||
pub number_byte: Style,
|
||||
pub number_kilo: Style,
|
||||
pub number_mega: Style,
|
||||
pub number_giga: Style,
|
||||
pub number_huge: Style,
|
||||
|
||||
pub unit_byte: Style,
|
||||
pub unit_kilo: Style,
|
||||
pub unit_mega: Style,
|
||||
pub unit_giga: Style,
|
||||
pub unit_huge: Style,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub struct Users {
|
||||
pub user_you: Style,
|
||||
pub user_someone_else: Style,
|
||||
pub group_yours: Style,
|
||||
pub group_not_yours: Style,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub struct Links {
|
||||
pub normal: Style,
|
||||
pub multi_link_file: Style,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub struct Git {
|
||||
pub new: Style,
|
||||
pub modified: Style,
|
||||
pub deleted: Style,
|
||||
pub renamed: Style,
|
||||
pub typechange: Style,
|
||||
pub ignored: Style,
|
||||
pub conflicted: Style,
|
||||
}
|
||||
|
||||
impl UiStyles {
|
||||
pub fn plain() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl UiStyles {
|
||||
|
||||
/// Sets a value on this set of colours using one of the keys understood
|
||||
/// by the `LS_COLORS` environment variable. Invalid keys set nothing, but
|
||||
/// return false.
|
||||
pub fn set_ls(&mut self, pair: &Pair<'_>) -> bool {
|
||||
match pair.key {
|
||||
"di" => self.filekinds.directory = pair.to_style(), // DIR
|
||||
"ex" => self.filekinds.executable = pair.to_style(), // EXEC
|
||||
"fi" => self.filekinds.normal = pair.to_style(), // FILE
|
||||
"pi" => self.filekinds.pipe = pair.to_style(), // FIFO
|
||||
"so" => self.filekinds.socket = pair.to_style(), // SOCK
|
||||
"bd" => self.filekinds.block_device = pair.to_style(), // BLK
|
||||
"cd" => self.filekinds.char_device = pair.to_style(), // CHR
|
||||
"ln" => self.filekinds.symlink = pair.to_style(), // LINK
|
||||
"or" => self.broken_symlink = pair.to_style(), // ORPHAN
|
||||
_ => return false,
|
||||
// Codes we don’t do anything with:
|
||||
// MULTIHARDLINK, DOOR, SETUID, SETGID, CAPABILITY,
|
||||
// STICKY_OTHER_WRITABLE, OTHER_WRITABLE, STICKY, MISSING
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Sets a value on this set of colours using one of the keys understood
|
||||
/// by the `EXA_COLORS` environment variable. Invalid keys set nothing,
|
||||
/// but return false. This doesn’t take the `LS_COLORS` keys into account,
|
||||
/// so `set_ls` should have been run first.
|
||||
pub fn set_exa(&mut self, pair: &Pair<'_>) -> bool {
|
||||
match pair.key {
|
||||
"ur" => self.perms.user_read = pair.to_style(),
|
||||
"uw" => self.perms.user_write = pair.to_style(),
|
||||
"ux" => self.perms.user_execute_file = pair.to_style(),
|
||||
"ue" => self.perms.user_execute_other = pair.to_style(),
|
||||
"gr" => self.perms.group_read = pair.to_style(),
|
||||
"gw" => self.perms.group_write = pair.to_style(),
|
||||
"gx" => self.perms.group_execute = pair.to_style(),
|
||||
"tr" => self.perms.other_read = pair.to_style(),
|
||||
"tw" => self.perms.other_write = pair.to_style(),
|
||||
"tx" => self.perms.other_execute = pair.to_style(),
|
||||
"su" => self.perms.special_user_file = pair.to_style(),
|
||||
"sf" => self.perms.special_other = pair.to_style(),
|
||||
"xa" => self.perms.attribute = pair.to_style(),
|
||||
|
||||
"sn" => self.set_number_style(pair.to_style()),
|
||||
"sb" => self.set_unit_style(pair.to_style()),
|
||||
"nb" => self.size.number_byte = pair.to_style(),
|
||||
"nk" => self.size.number_kilo = pair.to_style(),
|
||||
"nm" => self.size.number_mega = pair.to_style(),
|
||||
"ng" => self.size.number_giga = pair.to_style(),
|
||||
"nh" => self.size.number_huge = pair.to_style(),
|
||||
"ub" => self.size.unit_byte = pair.to_style(),
|
||||
"uk" => self.size.unit_kilo = pair.to_style(),
|
||||
"um" => self.size.unit_mega = pair.to_style(),
|
||||
"ug" => self.size.unit_giga = pair.to_style(),
|
||||
"uh" => self.size.unit_huge = pair.to_style(),
|
||||
"df" => self.size.major = pair.to_style(),
|
||||
"ds" => self.size.minor = pair.to_style(),
|
||||
|
||||
"uu" => self.users.user_you = pair.to_style(),
|
||||
"un" => self.users.user_someone_else = pair.to_style(),
|
||||
"gu" => self.users.group_yours = pair.to_style(),
|
||||
"gn" => self.users.group_not_yours = pair.to_style(),
|
||||
|
||||
"lc" => self.links.normal = pair.to_style(),
|
||||
"lm" => self.links.multi_link_file = pair.to_style(),
|
||||
|
||||
"ga" => self.git.new = pair.to_style(),
|
||||
"gm" => self.git.modified = pair.to_style(),
|
||||
"gd" => self.git.deleted = pair.to_style(),
|
||||
"gv" => self.git.renamed = pair.to_style(),
|
||||
"gt" => self.git.typechange = pair.to_style(),
|
||||
|
||||
"xx" => self.punctuation = pair.to_style(),
|
||||
"da" => self.date = pair.to_style(),
|
||||
"in" => self.inode = pair.to_style(),
|
||||
"bl" => self.blocks = pair.to_style(),
|
||||
"hd" => self.header = pair.to_style(),
|
||||
"lp" => self.symlink_path = pair.to_style(),
|
||||
"cc" => self.control_char = pair.to_style(),
|
||||
"bO" => self.broken_path_overlay = pair.to_style(),
|
||||
|
||||
_ => return false,
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn set_number_style(&mut self, style: Style) {
|
||||
self.size.number_byte = style;
|
||||
self.size.number_kilo = style;
|
||||
self.size.number_mega = style;
|
||||
self.size.number_giga = style;
|
||||
self.size.number_huge = style;
|
||||
}
|
||||
|
||||
pub fn set_unit_style(&mut self, style: Style) {
|
||||
self.size.unit_byte = style;
|
||||
self.size.unit_kilo = style;
|
||||
self.size.unit_mega = style;
|
||||
self.size.unit_giga = style;
|
||||
self.size.unit_huge = style;
|
||||
}
|
||||
}
|
79
xtests/icons.toml
Normal file
79
xtests/icons.toml
Normal file
@ -0,0 +1,79 @@
|
||||
# view icons tests
|
||||
|
||||
[[cmd]]
|
||||
name = "‘exa -1 --icons’ shows icons next to file names in lines mode"
|
||||
shell = "exa -1 --icons /testcases/files"
|
||||
stdout = { file = "outputs/files_oneline_icons.ansitxt" }
|
||||
stderr = { empty = true }
|
||||
status = 0
|
||||
tags = [ 'oneline', 'icons' ]
|
||||
|
||||
[[cmd]]
|
||||
name = "‘exa --icons’ shows icons next to file names in grid mode"
|
||||
shell = "exa --icons /testcases/files"
|
||||
environment = { COLUMNS = "80" }
|
||||
stdout = { file = "outputs/files_grid_icons.ansitxt" }
|
||||
stderr = { empty = true }
|
||||
status = 0
|
||||
tags = [ 'env', 'grid', 'icons' ]
|
||||
|
||||
[[cmd]]
|
||||
name = "‘exa -l --icons’ shows icons next to file names in long mode"
|
||||
shell = "exa -l --icons /testcases/files"
|
||||
stdout = { file = "outputs/files_long_icons.ansitxt" }
|
||||
stderr = { empty = true }
|
||||
status = 0
|
||||
tags = [ 'long', 'icons' ]
|
||||
|
||||
[[cmd]]
|
||||
name = "‘exa -lG --icons’ shows icons next to file names in long-grid mode"
|
||||
shell = "exa -lG --icons /testcases/files"
|
||||
environment = { COLUMNS = "80" }
|
||||
stdout = { file = "outputs/files_long_grid_icons.ansitxt" }
|
||||
stderr = { empty = true }
|
||||
status = 0
|
||||
tags = [ 'env', 'long', 'grid', 'icons' ]
|
||||
|
||||
[[cmd]]
|
||||
name = "‘exa -T --icons’ shows icons next to file names in tree mode"
|
||||
shell = "exa -T --icons /testcases/files"
|
||||
environment = { COLUMNS = "80" }
|
||||
stdout = { file = "outputs/files_tree_icons.ansitxt" }
|
||||
stderr = { empty = true }
|
||||
status = 0
|
||||
tags = [ 'tree', 'icons' ]
|
||||
|
||||
[[cmd]]
|
||||
name = "‘exa -lT --icons’ shows icons next to file names in long-tree mode"
|
||||
shell = "exa -lT --icons /testcases/files"
|
||||
stdout = { file = "outputs/files_long_tree_icons.ansitxt" }
|
||||
stderr = { empty = true }
|
||||
status = 0
|
||||
tags = [ 'long', 'tree', 'icons' ]
|
||||
|
||||
|
||||
# file type icons tests
|
||||
|
||||
[[cmd]]
|
||||
name = "‘exa -1 --icons’ produces icons based on file types"
|
||||
shell = "exa -1 --icons /testcases/file-names-exts"
|
||||
stdout = { file = "outputs/exts_oneline_icons.ansitxt" }
|
||||
stderr = { empty = true }
|
||||
status = 0
|
||||
tags = [ 'oneline', 'icons' ]
|
||||
|
||||
[[cmd]]
|
||||
name = "‘exa -1 --icons’ produces icons based on permissions"
|
||||
shell = "exa -1 --icons /testcases/permissions"
|
||||
stdout = { file = "outputs/permissions_oneline_icons.ansitxt" }
|
||||
stderr = { empty = true }
|
||||
status = 0
|
||||
tags = [ 'oneline', 'icons' ]
|
||||
|
||||
[[cmd]]
|
||||
name = "‘exa -1 --icons’ produces icons for links"
|
||||
shell = "exa -1 --icons /testcases/links"
|
||||
stdout = { file = "outputs/links_oneline_icons.ansitxt" }
|
||||
stderr = { empty = true }
|
||||
status = 0
|
||||
tags = [ 'oneline', 'icons' ]
|
26
xtests/outputs/exts_oneline_icons.ansitxt
Normal file
26
xtests/outputs/exts_oneline_icons.ansitxt
Normal file
@ -0,0 +1,26 @@
|
||||
[38;5;244m[0m [38;5;244m#SAVEFILE#[0m
|
||||
[38;5;244m[0m [38;5;244mbackup~[0m
|
||||
[38;5;137m[0m [38;5;137mcompiled.class[0m
|
||||
compiled.coffee
|
||||
[38;5;137m[0m [38;5;137mcompiled.js[0m
|
||||
[38;5;137m[0m [38;5;137mcompiled.o[0m
|
||||
[31m[0m [31mcompressed.deb[0m
|
||||
[31m[0m [31mcompressed.tar.gz[0m
|
||||
[31m[0m [31mcompressed.tar.xz[0m
|
||||
[31m[0m [31mcompressed.tgz[0m
|
||||
[31m[0m [31mcompressed.txz[0m
|
||||
[31m[0m [31mCOMPRESSED.ZIP[0m
|
||||
[38;5;109m[0m [38;5;109mcrypto.asc[0m
|
||||
[38;5;109m[0m [38;5;109mcrypto.signature[0m
|
||||
[38;5;105m[0m [38;5;105mdocument.pdf[0m
|
||||
[38;5;105m[0m [38;5;105mDOCUMENT.XLSX[0m
|
||||
[38;5;244m[0m [38;5;244mfile.tmp[0m
|
||||
[38;5;133m[0m [38;5;133mIMAGE.PNG[0m
|
||||
[38;5;133m[0m [38;5;133mimage.svg[0m
|
||||
[38;5;93m[0m [38;5;93mlossless.flac[0m
|
||||
[38;5;93m[0m [38;5;93mlossless.wav[0m
|
||||
[33m[0m [1;4;33mMakefile[0m
|
||||
[38;5;92m[0m [38;5;92mmusic.mp3[0m
|
||||
[38;5;92m[0m [38;5;92mMUSIC.OGG[0m
|
||||
[38;5;135m[0m [38;5;135mVIDEO.AVI[0m
|
||||
[38;5;135m[0m [38;5;135mvideo.wmv[0m
|
6
xtests/outputs/files_grid_icons.ansitxt
Normal file
6
xtests/outputs/files_grid_icons.ansitxt
Normal file
@ -0,0 +1,6 @@
|
||||
1_bytes 3_bytes 5_bytes 7_bytes 9_bytes 11_bytes 13_bytes
|
||||
1_KiB 3_KiB 5_KiB 7_KiB 9_KiB 11_KiB 13_KiB
|
||||
1_MiB 3_MiB 5_MiB 7_MiB 9_MiB 11_MiB 13_MiB
|
||||
2_bytes 4_bytes 6_bytes 8_bytes 10_bytes 12_bytes
|
||||
2_KiB 4_KiB 6_KiB 8_KiB 10_KiB 12_KiB
|
||||
2_MiB 4_MiB 6_MiB 8_MiB 10_MiB 12_MiB
|
39
xtests/outputs/files_long_grid_icons.ansitxt
Normal file
39
xtests/outputs/files_long_grid_icons.ansitxt
Normal file
@ -0,0 +1,39 @@
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m1[0m cassowary [34m 1 Jan 12:34[0m 1_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m1.0[0m[32mk[0m cassowary [34m 1 Jan 12:34[0m 1_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m1.0[0m[32mM[0m cassowary [34m 1 Jan 12:34[0m 1_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m2[0m cassowary [34m 1 Jan 12:34[0m 2_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m2.0[0m[32mk[0m cassowary [34m 1 Jan 12:34[0m 2_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m2.1[0m[32mM[0m cassowary [34m 1 Jan 12:34[0m 2_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m3[0m cassowary [34m 1 Jan 12:34[0m 3_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m3.1[0m[32mk[0m cassowary [34m 1 Jan 12:34[0m 3_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m3.1[0m[32mM[0m cassowary [34m 1 Jan 12:34[0m 3_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m4[0m cassowary [34m 1 Jan 12:34[0m 4_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m4.1[0m[32mk[0m cassowary [34m 1 Jan 12:34[0m 4_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m4.2[0m[32mM[0m cassowary [34m 1 Jan 12:34[0m 4_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m5[0m cassowary [34m 1 Jan 12:34[0m 5_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m5.1[0m[32mk[0m cassowary [34m 1 Jan 12:34[0m 5_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m5.2[0m[32mM[0m cassowary [34m 1 Jan 12:34[0m 5_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m6[0m cassowary [34m 1 Jan 12:34[0m 6_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m6.1[0m[32mk[0m cassowary [34m 1 Jan 12:34[0m 6_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m6.3[0m[32mM[0m cassowary [34m 1 Jan 12:34[0m 6_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m7[0m cassowary [34m 1 Jan 12:34[0m 7_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m7.2[0m[32mk[0m cassowary [34m 1 Jan 12:34[0m 7_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m7.3[0m[32mM[0m cassowary [34m 1 Jan 12:34[0m 7_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m8[0m cassowary [34m 1 Jan 12:34[0m 8_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m8.2[0m[32mk[0m cassowary [34m 1 Jan 12:34[0m 8_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m8.4[0m[32mM[0m cassowary [34m 1 Jan 12:34[0m 8_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m9[0m cassowary [34m 1 Jan 12:34[0m 9_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m9.2[0m[32mk[0m cassowary [34m 1 Jan 12:34[0m 9_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m9.4[0m[32mM[0m cassowary [34m 1 Jan 12:34[0m 9_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m10[0m cassowary [34m 1 Jan 12:34[0m 10_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m10[0m[32mk[0m cassowary [34m 1 Jan 12:34[0m 10_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m10[0m[32mM[0m cassowary [34m 1 Jan 12:34[0m 10_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m11[0m cassowary [34m 1 Jan 12:34[0m 11_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m11[0m[32mk[0m cassowary [34m 1 Jan 12:34[0m 11_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m11[0m[32mM[0m cassowary [34m 1 Jan 12:34[0m 11_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m12[0m cassowary [34m 1 Jan 12:34[0m 12_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m12[0m[32mk[0m cassowary [34m 1 Jan 12:34[0m 12_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m12[0m[32mM[0m cassowary [34m 1 Jan 12:34[0m 12_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m13[0m cassowary [34m 1 Jan 12:34[0m 13_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m13[0m[32mk[0m cassowary [34m 1 Jan 12:34[0m 13_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m13[0m[32mM[0m cassowary [34m 1 Jan 12:34[0m 13_MiB
|
39
xtests/outputs/files_long_icons.ansitxt
Normal file
39
xtests/outputs/files_long_icons.ansitxt
Normal file
@ -0,0 +1,39 @@
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m1[0m cassowary [34m 1 Jan 12:34[0m 1_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m1.0[0m[32mk[0m cassowary [34m 1 Jan 12:34[0m 1_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m1.0[0m[32mM[0m cassowary [34m 1 Jan 12:34[0m 1_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m2[0m cassowary [34m 1 Jan 12:34[0m 2_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m2.0[0m[32mk[0m cassowary [34m 1 Jan 12:34[0m 2_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m2.1[0m[32mM[0m cassowary [34m 1 Jan 12:34[0m 2_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m3[0m cassowary [34m 1 Jan 12:34[0m 3_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m3.1[0m[32mk[0m cassowary [34m 1 Jan 12:34[0m 3_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m3.1[0m[32mM[0m cassowary [34m 1 Jan 12:34[0m 3_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m4[0m cassowary [34m 1 Jan 12:34[0m 4_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m4.1[0m[32mk[0m cassowary [34m 1 Jan 12:34[0m 4_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m4.2[0m[32mM[0m cassowary [34m 1 Jan 12:34[0m 4_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m5[0m cassowary [34m 1 Jan 12:34[0m 5_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m5.1[0m[32mk[0m cassowary [34m 1 Jan 12:34[0m 5_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m5.2[0m[32mM[0m cassowary [34m 1 Jan 12:34[0m 5_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m6[0m cassowary [34m 1 Jan 12:34[0m 6_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m6.1[0m[32mk[0m cassowary [34m 1 Jan 12:34[0m 6_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m6.3[0m[32mM[0m cassowary [34m 1 Jan 12:34[0m 6_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m7[0m cassowary [34m 1 Jan 12:34[0m 7_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m7.2[0m[32mk[0m cassowary [34m 1 Jan 12:34[0m 7_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m7.3[0m[32mM[0m cassowary [34m 1 Jan 12:34[0m 7_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m8[0m cassowary [34m 1 Jan 12:34[0m 8_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m8.2[0m[32mk[0m cassowary [34m 1 Jan 12:34[0m 8_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m8.4[0m[32mM[0m cassowary [34m 1 Jan 12:34[0m 8_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m9[0m cassowary [34m 1 Jan 12:34[0m 9_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m9.2[0m[32mk[0m cassowary [34m 1 Jan 12:34[0m 9_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m9.4[0m[32mM[0m cassowary [34m 1 Jan 12:34[0m 9_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m10[0m cassowary [34m 1 Jan 12:34[0m 10_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m10[0m[32mk[0m cassowary [34m 1 Jan 12:34[0m 10_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m10[0m[32mM[0m cassowary [34m 1 Jan 12:34[0m 10_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m11[0m cassowary [34m 1 Jan 12:34[0m 11_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m11[0m[32mk[0m cassowary [34m 1 Jan 12:34[0m 11_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m11[0m[32mM[0m cassowary [34m 1 Jan 12:34[0m 11_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m12[0m cassowary [34m 1 Jan 12:34[0m 12_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m12[0m[32mk[0m cassowary [34m 1 Jan 12:34[0m 12_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m12[0m[32mM[0m cassowary [34m 1 Jan 12:34[0m 12_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m13[0m cassowary [34m 1 Jan 12:34[0m 13_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m13[0m[32mk[0m cassowary [34m 1 Jan 12:34[0m 13_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m13[0m[32mM[0m cassowary [34m 1 Jan 12:34[0m 13_MiB
|
40
xtests/outputs/files_long_tree_icons.ansitxt
Normal file
40
xtests/outputs/files_long_tree_icons.ansitxt
Normal file
@ -0,0 +1,40 @@
|
||||
[1;34md[33mr[31mw[32mx[0m[33mr[31mw[32mx[33mr[38;5;244m-[32mx[0m [38;5;244m-[0m [1;33mvagrant[0m [34m18 Oct 00:18[0m [36m/testcases/[1;34mfiles[0m
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m1[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m├──[0m 1_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m1.0[0m[32mk[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m├──[0m 1_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m1.0[0m[32mM[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m├──[0m 1_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m2[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m├──[0m 2_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m2.0[0m[32mk[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m├──[0m 2_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m2.1[0m[32mM[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m├──[0m 2_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m3[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m├──[0m 3_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m3.1[0m[32mk[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m├──[0m 3_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m3.1[0m[32mM[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m├──[0m 3_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m4[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m├──[0m 4_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m4.1[0m[32mk[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m├──[0m 4_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m4.2[0m[32mM[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m├──[0m 4_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m5[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m├──[0m 5_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m5.1[0m[32mk[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m├──[0m 5_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m5.2[0m[32mM[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m├──[0m 5_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m6[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m├──[0m 6_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m6.1[0m[32mk[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m├──[0m 6_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m6.3[0m[32mM[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m├──[0m 6_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m7[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m├──[0m 7_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m7.2[0m[32mk[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m├──[0m 7_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m7.3[0m[32mM[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m├──[0m 7_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m8[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m├──[0m 8_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m8.2[0m[32mk[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m├──[0m 8_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m8.4[0m[32mM[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m├──[0m 8_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m9[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m├──[0m 9_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m9.2[0m[32mk[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m├──[0m 9_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m9.4[0m[32mM[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m├──[0m 9_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m10[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m├──[0m 10_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m10[0m[32mk[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m├──[0m 10_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m10[0m[32mM[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m├──[0m 10_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m11[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m├──[0m 11_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m11[0m[32mk[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m├──[0m 11_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m11[0m[32mM[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m├──[0m 11_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m12[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m├──[0m 12_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m12[0m[32mk[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m├──[0m 12_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m12[0m[32mM[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m├──[0m 12_MiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m13[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m├──[0m 13_bytes
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m13[0m[32mk[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m├──[0m 13_KiB
|
||||
.[1;33mr[31mw[0m[38;5;244m-[33mr[38;5;244m--[33mr[38;5;244m--[0m [1;32m13[0m[32mM[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m└──[0m 13_MiB
|
39
xtests/outputs/files_oneline_icons.ansitxt
Normal file
39
xtests/outputs/files_oneline_icons.ansitxt
Normal file
@ -0,0 +1,39 @@
|
||||
1_bytes
|
||||
1_KiB
|
||||
1_MiB
|
||||
2_bytes
|
||||
2_KiB
|
||||
2_MiB
|
||||
3_bytes
|
||||
3_KiB
|
||||
3_MiB
|
||||
4_bytes
|
||||
4_KiB
|
||||
4_MiB
|
||||
5_bytes
|
||||
5_KiB
|
||||
5_MiB
|
||||
6_bytes
|
||||
6_KiB
|
||||
6_MiB
|
||||
7_bytes
|
||||
7_KiB
|
||||
7_MiB
|
||||
8_bytes
|
||||
8_KiB
|
||||
8_MiB
|
||||
9_bytes
|
||||
9_KiB
|
||||
9_MiB
|
||||
10_bytes
|
||||
10_KiB
|
||||
10_MiB
|
||||
11_bytes
|
||||
11_KiB
|
||||
11_MiB
|
||||
12_bytes
|
||||
12_KiB
|
||||
12_MiB
|
||||
13_bytes
|
||||
13_KiB
|
||||
13_MiB
|
40
xtests/outputs/files_tree_icons.ansitxt
Normal file
40
xtests/outputs/files_tree_icons.ansitxt
Normal file
@ -0,0 +1,40 @@
|
||||
[36m/testcases/[1;34mfiles[0m
|
||||
[38;5;244m├──[0m 1_bytes
|
||||
[38;5;244m├──[0m 1_KiB
|
||||
[38;5;244m├──[0m 1_MiB
|
||||
[38;5;244m├──[0m 2_bytes
|
||||
[38;5;244m├──[0m 2_KiB
|
||||
[38;5;244m├──[0m 2_MiB
|
||||
[38;5;244m├──[0m 3_bytes
|
||||
[38;5;244m├──[0m 3_KiB
|
||||
[38;5;244m├──[0m 3_MiB
|
||||
[38;5;244m├──[0m 4_bytes
|
||||
[38;5;244m├──[0m 4_KiB
|
||||
[38;5;244m├──[0m 4_MiB
|
||||
[38;5;244m├──[0m 5_bytes
|
||||
[38;5;244m├──[0m 5_KiB
|
||||
[38;5;244m├──[0m 5_MiB
|
||||
[38;5;244m├──[0m 6_bytes
|
||||
[38;5;244m├──[0m 6_KiB
|
||||
[38;5;244m├──[0m 6_MiB
|
||||
[38;5;244m├──[0m 7_bytes
|
||||
[38;5;244m├──[0m 7_KiB
|
||||
[38;5;244m├──[0m 7_MiB
|
||||
[38;5;244m├──[0m 8_bytes
|
||||
[38;5;244m├──[0m 8_KiB
|
||||
[38;5;244m├──[0m 8_MiB
|
||||
[38;5;244m├──[0m 9_bytes
|
||||
[38;5;244m├──[0m 9_KiB
|
||||
[38;5;244m├──[0m 9_MiB
|
||||
[38;5;244m├──[0m 10_bytes
|
||||
[38;5;244m├──[0m 10_KiB
|
||||
[38;5;244m├──[0m 10_MiB
|
||||
[38;5;244m├──[0m 11_bytes
|
||||
[38;5;244m├──[0m 11_KiB
|
||||
[38;5;244m├──[0m 11_MiB
|
||||
[38;5;244m├──[0m 12_bytes
|
||||
[38;5;244m├──[0m 12_KiB
|
||||
[38;5;244m├──[0m 12_MiB
|
||||
[38;5;244m├──[0m 13_bytes
|
||||
[38;5;244m├──[0m 13_KiB
|
||||
[38;5;244m└──[0m 13_MiB
|
10
xtests/outputs/links_oneline_icons.ansitxt
Normal file
10
xtests/outputs/links_oneline_icons.ansitxt
Normal file
@ -0,0 +1,10 @@
|
||||
[36mbroken[0m [31m->[0m [4;31mnowhere[0m
|
||||
[36mcurrent_dir[0m [38;5;244m->[0m [1;34m.[0m
|
||||
[36mforbidden[0m [31m->[0m [4;31m/proc/1/root[0m
|
||||
[36mitself[0m [31m->[0m [4;31mitself[0m
|
||||
[36mparent_dir[0m [38;5;244m->[0m [1;34m..[0m
|
||||
[36mroot[0m [38;5;244m->[0m [1;34m/[0m
|
||||
some_file
|
||||
[36msome_file_absolute[0m [38;5;244m->[0m [36m/testcases/links/[0msome_file
|
||||
[36msome_file_relative[0m [38;5;244m->[0m some_file
|
||||
[36musr[0m [38;5;244m->[0m [36m/[1;34musr[0m
|
22
xtests/outputs/permissions_oneline_icons.ansitxt
Normal file
22
xtests/outputs/permissions_oneline_icons.ansitxt
Normal file
@ -0,0 +1,22 @@
|
||||
000
|
||||
001
|
||||
002
|
||||
004
|
||||
010
|
||||
020
|
||||
040
|
||||
[1;32m100[0m
|
||||
200
|
||||
400
|
||||
644
|
||||
[1;32m755[0m
|
||||
[1;32m777[0m
|
||||
1000
|
||||
1001
|
||||
2000
|
||||
2010
|
||||
4000
|
||||
[1;32m4100[0m
|
||||
7666
|
||||
[1;32m7777[0m
|
||||
[1;34mforbidden-directory[0m
|
Loading…
Reference in New Issue
Block a user