exa/src/output/file_name.rs

216 lines
8.3 KiB
Rust
Raw Normal View History

2017-05-01 21:26:16 +00:00
use std::path::Path;
use ansi_term::{ANSIString, Style};
use fs::{File, FileTarget};
use output::Colours;
use output::escape;
use output::cell::TextCellContents;
2017-05-07 14:14:06 +00:00
/// 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> {
2017-05-07 14:14:06 +00:00
/// A reference to the file that we're getting the name of.
file: &'a File<'dir>,
2017-05-07 14:14:06 +00:00
/// The colours used to paint the file name and its surrounding text.
colours: &'a Colours,
2017-05-07 14:14:06 +00:00
/// The file that this file points to if it's a link.
target: Option<FileTarget<'dir>>,
/// How to handle displaying links.
link_style: LinkStyle,
}
2017-05-07 14:14:06 +00:00
impl<'a, 'dir> FileName<'a, 'dir> {
2017-05-07 14:14:06 +00:00
/// 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, colours: &'a Colours) -> FileName<'a, 'dir> {
2017-05-07 09:44:09 +00:00
let target = if file.is_link() { Some(file.link_target()) }
else { None };
FileName {
file: file,
colours: colours,
2017-05-07 09:44:09 +00:00
target: target,
link_style: link_style,
}
}
2017-05-07 14:14:06 +00:00
/// 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, classify: bool) -> TextCellContents {
let mut bits = Vec::new();
if self.file.dir.is_none() {
if let Some(parent) = self.file.path.parent() {
2017-05-01 21:26:16 +00:00
self.add_parent_bits(&mut bits, parent);
}
}
if !self.file.name.is_empty() {
for bit in self.coloured_file_name() {
bits.push(bit);
}
}
if let (LinkStyle::FullLinkPaths, Some(ref target)) = (self.link_style, self.target.as_ref()) {
match **target {
FileTarget::Ok(ref target) => {
bits.push(Style::default().paint(" "));
bits.push(self.colours.punctuation.paint("->"));
bits.push(Style::default().paint(" "));
if let Some(parent) = target.path.parent() {
2017-05-01 21:26:16 +00:00
self.add_parent_bits(&mut bits, parent);
}
if !target.name.is_empty() {
let target = FileName::new(&target, LinkStyle::FullLinkPaths, self.colours);
for bit in target.coloured_file_name() {
bits.push(bit);
}
}
},
FileTarget::Broken(ref broken_path) => {
bits.push(Style::default().paint(" "));
bits.push(self.colours.broken_arrow.paint("->"));
bits.push(Style::default().paint(" "));
escape(broken_path.display().to_string(), &mut bits, self.colours.broken_filename, self.colours.control_char.underline());
},
FileTarget::Err(_) => {
// Do nothing -- the error gets displayed on the next line
2017-05-07 09:44:09 +00:00
},
}
}
else if classify {
if let Some(class) = self.classify_char() {
bits.push(Style::default().paint(class));
}
}
bits.into()
}
2017-05-07 14:14:06 +00:00
2017-05-01 21:26:16 +00:00
/// 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) {
let coconut = parent.components().count();
if coconut == 1 && parent.has_root() {
bits.push(self.colours.symlink_path.paint("/"));
}
else if coconut >= 1 {
escape(parent.to_string_lossy().to_string(), bits, self.colours.symlink_path, self.colours.control_char);
bits.push(self.colours.symlink_path.paint("/"));
}
}
2017-05-07 14:14:06 +00:00
2017-05-01 21:26:16 +00:00
/// The character to be displayed after a file when classifying is on, if
/// the files type has one associated with it.
fn classify_char(&self) -> Option<&'static str> {
if self.file.is_executable_file() {
Some("*")
} else if self.file.is_directory() {
Some("/")
} else if self.file.is_pipe() {
Some("|")
} else if self.file.is_link() {
Some("@")
} else if self.file.is_socket() {
Some("=")
} else {
None
}
}
2017-05-07 14:14:06 +00:00
/// Returns at least one ANSI-highlighted string representing this files
/// name using the given set of colours.
///
/// Ordinarily, this will be just one string: the files complete name,
/// coloured according to its file type. If the name contains control
/// characters such as newlines or escapes, though, we cant just print them
/// to the screen directly, because then therell be newlines in weird places.
///
/// So in that situation, those characters will be escaped and highlighted in
/// a different colour.
fn coloured_file_name<'unused>(&self) -> Vec<ANSIString<'unused>> {
2017-05-01 14:43:27 +00:00
let file_style = self.style();
let mut bits = Vec::new();
escape(self.file.name.clone(), &mut bits, file_style, self.colours.control_char);
bits
}
2017-05-07 14:14:06 +00:00
/// 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.
2017-05-01 14:43:27 +00:00
pub fn style(&self) -> Style {
2017-05-07 14:14:06 +00:00
// 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;
}
}
}
2017-05-07 14:14:06 +00:00
// Otherwise, just apply a bunch of rules in order. For example,
// executable image files should be executable rather than images.
match self.file {
f if f.is_directory() => self.colours.filetypes.directory,
f if f.is_executable_file() => self.colours.filetypes.executable,
f if f.is_link() => self.colours.filetypes.symlink,
f if f.is_pipe() => self.colours.filetypes.pipe,
f if f.is_char_device()
| f.is_block_device() => self.colours.filetypes.device,
f if f.is_socket() => self.colours.filetypes.socket,
f if !f.is_file() => self.colours.filetypes.special,
f if f.is_immediate() => self.colours.filetypes.immediate,
f if f.is_image() => self.colours.filetypes.image,
f if f.is_video() => self.colours.filetypes.video,
f if f.is_music() => self.colours.filetypes.music,
f if f.is_lossless() => self.colours.filetypes.lossless,
f if f.is_crypto() => self.colours.filetypes.crypto,
f if f.is_document() => self.colours.filetypes.document,
f if f.is_compressed() => self.colours.filetypes.compressed,
f if f.is_temp() => self.colours.filetypes.temp,
f if f.is_compiled() => self.colours.filetypes.compiled,
_ => self.colours.filetypes.normal,
}
}
}
2017-05-07 14:14:06 +00:00
/// 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,
}