Merge branch 'more-file-name-fields'

This commit is contained in:
Benjamin Sago 2017-05-07 15:31:55 +01:00
commit e819dd95e9
9 changed files with 142 additions and 31 deletions

View File

@ -407,11 +407,24 @@ pub enum FileTarget<'dir> {
Broken(PathBuf), Broken(PathBuf),
/// There was an IO error when following the link. This can happen if the /// There was an IO error when following the link. This can happen if the
/// file isn't a link to begin with, but also if, say, we don't have /// file isnt a link to begin with, but also if, say, we dont have
/// permission to follow it. /// permission to follow it.
Err(IOError), Err(IOError),
} }
impl<'dir> FileTarget<'dir> {
/// Whether this link doesnt lead to a file, for whatever reason. This
/// gets used to determine how to highlight the link in grid views.
pub fn is_broken(&self) -> bool {
match self {
&FileTarget::Ok(_) => false,
&FileTarget::Broken(_) => true,
&FileTarget::Err(_) => true,
}
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {

View File

@ -4,8 +4,9 @@ use getopts;
use output::Colours; use output::Colours;
use output::{Grid, Details, GridDetails, Lines}; use output::{Grid, Details, GridDetails, Lines};
use options::{FileFilter, DirAction, Misfire};
use output::column::{Columns, TimeTypes, SizeFormat}; use output::column::{Columns, TimeTypes, SizeFormat};
use output::file_name::Classify;
use options::{FileFilter, DirAction, Misfire};
use term::dimensions; use term::dimensions;
use fs::feature::xattr; use fs::feature::xattr;
@ -58,7 +59,7 @@ impl View {
filter: filter.clone(), filter: filter.clone(),
xattr: xattr::ENABLED && matches.opt_present("extended"), xattr: xattr::ENABLED && matches.opt_present("extended"),
colours: colours, colours: colours,
classify: matches.opt_present("classify"), classify: Classify::deduce(matches),
}; };
Ok(details) Ok(details)
@ -87,8 +88,7 @@ impl View {
}; };
let other_options_scan = || { let other_options_scan = || {
let classify = matches.opt_present("classify"); let classify = Classify::deduce(matches);
let term_colours = TerminalColours::deduce(matches)?; let term_colours = TerminalColours::deduce(matches)?;
let term_width = TerminalWidth::deduce()?; let term_width = TerminalWidth::deduce()?;
@ -366,3 +366,12 @@ impl TerminalColours {
} }
} }
} }
impl Classify {
fn deduce(matches: &getopts::Matches) -> Classify {
if matches.opt_present("classify") { Classify::AddFileIndicators }
else { Classify::JustFilenames }
}
}

View File

@ -101,7 +101,7 @@ use output::colours::Colours;
use output::column::{Alignment, Column, Columns, SizeFormat}; use output::column::{Alignment, Column, Columns, SizeFormat};
use output::cell::{TextCell, TextCellContents, DisplayWidth}; use output::cell::{TextCell, TextCellContents, DisplayWidth};
use output::tree::TreeTrunk; use output::tree::TreeTrunk;
use output::file_name::FileName; use output::file_name::{FileName, LinkStyle, Classify};
/// With the **Details** view, the output gets formatted into columns, with /// With the **Details** view, the output gets formatted into columns, with
@ -142,7 +142,7 @@ pub struct Details {
pub colours: Colours, pub colours: Colours,
/// Whether to show a file type indiccator. /// Whether to show a file type indiccator.
pub classify: bool, pub classify: Classify,
} }
/// The **environment** struct contains any data that could change between /// The **environment** struct contains any data that could change between
@ -310,7 +310,7 @@ impl Details {
let row = Row { let row = Row {
depth: depth, depth: depth,
cells: Some(egg.cells), cells: Some(egg.cells),
name: FileName::new(&egg.file, &self.colours).paint(true, self.classify).promote(), name: FileName::new(&egg.file, LinkStyle::FullLinkPaths, self.classify, &self.colours).paint().promote(),
last: index == num_eggs - 1, last: index == num_eggs - 1,
}; };
@ -443,8 +443,8 @@ impl<'a, U: Users+Groups+'a> Table<'a, U> {
self.rows.push(row); self.rows.push(row);
} }
pub fn filename(&self, file: File, links: bool) -> TextCellContents { pub fn filename(&self, file: File, links: LinkStyle) -> TextCellContents {
FileName::new(&file, &self.opts.colours).paint(links, self.opts.classify) FileName::new(&file, links, self.opts.classify, &self.opts.colours).paint()
} }
pub fn add_file_with_cells(&mut self, cells: Vec<TextCell>, name_cell: TextCell, depth: usize, last: bool) { pub fn add_file_with_cells(&mut self, cells: Vec<TextCell>, name_cell: TextCell, depth: usize, last: bool) {

View File

@ -8,20 +8,51 @@ use output::escape;
use output::cell::TextCellContents; use output::cell::TextCellContents;
/// 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: 'a> { pub struct FileName<'a, 'dir: 'a> {
file: &'a File<'dir>,
/// A reference to the file that we're getting the name of.
file: &'a File<'dir>,
/// The colours used to paint the file name and its surrounding text.
colours: &'a Colours, colours: &'a Colours,
/// The file that this file points to if it's a link.
target: Option<FileTarget<'dir>>,
/// How to handle displaying links.
link_style: LinkStyle,
/// Whether to append file class characters to file names.
classify: Classify,
} }
impl<'a, 'dir> FileName<'a, 'dir> { impl<'a, 'dir> FileName<'a, 'dir> {
pub fn new(file: &'a File<'dir>, colours: &'a Colours) -> FileName<'a, 'dir> {
/// Create a new `FileName` that prints the given files name, painting it
/// with the remaining arguments.
pub fn new(file: &'a File<'dir>, link_style: LinkStyle, classify: Classify, colours: &'a Colours) -> FileName<'a, 'dir> {
let target = if file.is_link() { Some(file.link_target()) }
else { None };
FileName { FileName {
file: file, file: file,
colours: colours, colours: colours,
target: target,
link_style: link_style,
classify: classify,
} }
} }
pub fn paint(&self, links: bool, classify: bool) -> TextCellContents {
/// Paints the name of the file using the colours, resulting in a vector
/// of coloured cells that can be printed to the terminal.
///
/// This method returns some `TextCellContents`, rather than a `TextCell`,
/// because for the last cell in a table, it doesnt need to have its
/// width calculated.
pub fn paint(&self) -> TextCellContents {
let mut bits = Vec::new(); let mut bits = Vec::new();
if self.file.dir.is_none() { if self.file.dir.is_none() {
@ -36,9 +67,9 @@ impl<'a, 'dir> FileName<'a, 'dir> {
} }
} }
if links && self.file.is_link() { if let (LinkStyle::FullLinkPaths, Some(ref target)) = (self.link_style, self.target.as_ref()) {
match self.file.link_target() { match **target {
FileTarget::Ok(target) => { FileTarget::Ok(ref target) => {
bits.push(Style::default().paint(" ")); bits.push(Style::default().paint(" "));
bits.push(self.colours.punctuation.paint("->")); bits.push(self.colours.punctuation.paint("->"));
bits.push(Style::default().paint(" ")); bits.push(Style::default().paint(" "));
@ -48,14 +79,14 @@ impl<'a, 'dir> FileName<'a, 'dir> {
} }
if !target.name.is_empty() { if !target.name.is_empty() {
let target = FileName::new(&target, self.colours); let target = FileName::new(&target, LinkStyle::FullLinkPaths, Classify::JustFilenames, self.colours);
for bit in target.coloured_file_name() { for bit in target.coloured_file_name() {
bits.push(bit); bits.push(bit);
} }
} }
}, },
FileTarget::Broken(broken_path) => { FileTarget::Broken(ref broken_path) => {
bits.push(Style::default().paint(" ")); bits.push(Style::default().paint(" "));
bits.push(self.colours.broken_arrow.paint("->")); bits.push(self.colours.broken_arrow.paint("->"));
bits.push(Style::default().paint(" ")); bits.push(Style::default().paint(" "));
@ -64,10 +95,10 @@ impl<'a, 'dir> FileName<'a, 'dir> {
FileTarget::Err(_) => { FileTarget::Err(_) => {
// Do nothing -- the error gets displayed on the next line // Do nothing -- the error gets displayed on the next line
} },
} }
} }
else if classify { else if let Classify::AddFileIndicators = self.classify {
if let Some(class) = self.classify_char() { if let Some(class) = self.classify_char() {
bits.push(Style::default().paint(class)); bits.push(Style::default().paint(class));
} }
@ -76,6 +107,7 @@ impl<'a, 'dir> FileName<'a, 'dir> {
bits.into() bits.into()
} }
/// Adds the bits of the parent path to the given bits vector. /// Adds the bits of the parent path to the given bits vector.
/// The path gets its characters escaped based on the colours. /// The path gets its characters escaped based on the colours.
fn add_parent_bits(&self, bits: &mut Vec<ANSIString>, parent: &Path) { fn add_parent_bits(&self, bits: &mut Vec<ANSIString>, parent: &Path) {
@ -90,6 +122,7 @@ impl<'a, 'dir> FileName<'a, 'dir> {
} }
} }
/// The character to be displayed after a file when classifying is on, if /// The character to be displayed after a file when classifying is on, if
/// the files type has one associated with it. /// the files type has one associated with it.
fn classify_char(&self) -> Option<&'static str> { fn classify_char(&self) -> Option<&'static str> {
@ -108,6 +141,7 @@ impl<'a, 'dir> FileName<'a, 'dir> {
} }
} }
/// Returns at least one ANSI-highlighted string representing this files /// Returns at least one ANSI-highlighted string representing this files
/// name using the given set of colours. /// name using the given set of colours.
/// ///
@ -125,7 +159,25 @@ impl<'a, 'dir> FileName<'a, 'dir> {
bits 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.
pub fn style(&self) -> Style { pub fn style(&self) -> Style {
// Override the style with the “broken link” style when this file is
// a link that we cant follow for whatever reason. This is used when
// theres no other place to show that the link doesnt work.
if let LinkStyle::JustFilenames = self.link_style {
if let Some(ref target) = self.target {
if target.is_broken() {
return self.colours.broken_arrow;
}
}
}
// Otherwise, just apply a bunch of rules in order. For example,
// executable image files should be executable rather than images.
match self.file { match self.file {
f if f.is_directory() => self.colours.filetypes.directory, f if f.is_directory() => self.colours.filetypes.directory,
f if f.is_executable_file() => self.colours.filetypes.executable, f if f.is_executable_file() => self.colours.filetypes.executable,
@ -149,3 +201,38 @@ impl<'a, 'dir> FileName<'a, 'dir> {
} }
} }
} }
/// 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)]
pub enum LinkStyle {
/// Just display the file names, but colour them differently if theyre
/// a broken link or cant be followed.
JustFilenames,
/// Display all files in their usual style, but follow each link with an
/// arrow pointing to their path, colouring the path differently if its
/// a broken link, and doing nothing if it cant be followed.
FullLinkPaths,
}
/// Whether to append file class characters to the file names.
#[derive(PartialEq, Debug, Copy, Clone)]
pub enum Classify {
/// Just display the file names, without any characters.
JustFilenames,
/// Add a character after the file name depending on what class of file
/// it is.
AddFileIndicators,
}
impl Default for Classify {
fn default() -> Classify {
Classify::JustFilenames
}
}

View File

@ -4,7 +4,7 @@ use term_grid as grid;
use fs::File; use fs::File;
use output::colours::Colours; use output::colours::Colours;
use output::file_name::FileName; use output::file_name::{FileName, LinkStyle, Classify};
#[derive(PartialEq, Debug, Copy, Clone)] #[derive(PartialEq, Debug, Copy, Clone)]
@ -12,7 +12,7 @@ pub struct Grid {
pub across: bool, pub across: bool,
pub console_width: usize, pub console_width: usize,
pub colours: Colours, pub colours: Colours,
pub classify: bool, pub classify: Classify,
} }
impl Grid { impl Grid {
@ -28,7 +28,7 @@ impl Grid {
grid.reserve(files.len()); grid.reserve(files.len());
for file in files.iter() { for file in files.iter() {
let filename = FileName::new(file, &self.colours).paint(false, self.classify); let filename = FileName::new(file, LinkStyle::JustFilenames, self.classify, &self.colours).paint();
let width = filename.width(); let width = filename.width();
grid.add(grid::Cell { grid.add(grid::Cell {
@ -43,7 +43,7 @@ impl Grid {
else { else {
// File names too long for a grid - drop down to just listing them! // File names too long for a grid - drop down to just listing them!
for file in files.iter() { for file in files.iter() {
let name_cell = FileName::new(file, &self.colours).paint(false, self.classify); let name_cell = FileName::new(file, LinkStyle::JustFilenames, self.classify, &self.colours).paint();
writeln!(w, "{}", name_cell.strings())?; writeln!(w, "{}", name_cell.strings())?;
} }
Ok(()) Ok(())

View File

@ -12,6 +12,8 @@ use output::cell::TextCell;
use output::column::Column; use output::column::Column;
use output::details::{Details, Table, Environment}; use output::details::{Details, Table, Environment};
use output::grid::Grid; use output::grid::Grid;
use output::file_name::LinkStyle;
#[derive(PartialEq, Debug, Clone)] #[derive(PartialEq, Debug, Clone)]
pub struct GridDetails { pub struct GridDetails {
@ -45,7 +47,7 @@ impl GridDetails {
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let file_names = files.into_iter() let file_names = files.into_iter()
.map(|file| first_table.filename(file, false).promote()) .map(|file| first_table.filename(file, LinkStyle::JustFilenames).promote())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
(cells, file_names) (cells, file_names)

View File

@ -4,21 +4,21 @@ use ansi_term::ANSIStrings;
use fs::File; use fs::File;
use output::file_name::FileName; use output::file_name::{FileName, LinkStyle, Classify};
use super::colours::Colours; use super::colours::Colours;
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub struct Lines { pub struct Lines {
pub colours: Colours, pub colours: Colours,
pub classify: bool, pub classify: Classify,
} }
/// The lines view literally just displays each file, line-by-line. /// The lines view literally just displays each file, line-by-line.
impl Lines { impl Lines {
pub fn view<W: Write>(&self, files: Vec<File>, w: &mut W) -> IOResult<()> { pub fn view<W: Write>(&self, files: Vec<File>, w: &mut W) -> IOResult<()> {
for file in files { for file in files {
let name_cell = FileName::new(&file, &self.colours).paint(true, self.classify); let name_cell = FileName::new(&file, LinkStyle::FullLinkPaths, self.classify, &self.colours).paint();
writeln!(w, "{}", ANSIStrings(&name_cell))?; writeln!(w, "{}", ANSIStrings(&name_cell))?;
} }
Ok(()) Ok(())

View File

@ -6,7 +6,7 @@ emoji: [🆒] invalid-utf8-4: [<5B>(<28>(] utf-8: pâté
escape: [\u{1b}] links vertical-tab: [\u{b}] escape: [\u{1b}] links vertical-tab: [\u{b}]
/testcases/file-names/links: /testcases/file-names/links:
another: [\n] broken subfile another: [\n] broken subfile
/testcases/file-names/new-line-dir: [\n]: /testcases/file-names/new-line-dir: [\n]:
another: [\n] subfile another: [\n] subfile

View File

@ -1,2 +1,2 @@
broken forbidden parent_dir some_file some_file_relative broken forbidden parent_dir some_file some_file_relative
current_dir itself root some_file_absolute usr current_dir itself root some_file_absolute usr