| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273 |
- use std::path::Path;
- use ansi_term::{ANSIString, Style};
- use fs::{File, FileTarget};
- use info::filetype::FileExtensions;
- use output::Colours;
- use output::escape;
- use output::cell::TextCellContents;
- /// Basically a file name factory.
- #[derive(Debug)]
- pub struct FileStyle {
- /// Whether to append file class characters to file names.
- pub classify: Classify,
- /// Mapping of file extensions to colours, to highlight regular files.
- pub exts: FileExtensions,
- }
- impl FileStyle {
- /// Create a new `FileName` that prints the given file’s name, painting it
- /// with the remaining arguments.
- pub fn for_file<'a, 'dir>(&'a self, file: &'a File<'dir>, colours: &'a Colours) -> FileName<'a, 'dir> {
- FileName {
- file, colours,
- link_style: LinkStyle::JustFilenames,
- exts: &self.exts,
- classify: self.classify,
- 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)]
- enum LinkStyle {
- /// Just display the file names, but colour them differently if they’re
- /// a broken link or can’t 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 it’s
- /// a broken link, and doing nothing if it can’t 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
- }
- }
- /// 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> {
- /// 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,
- /// 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,
- /// Mapping of file extensions to colours, to highlight regular files.
- exts: &'a FileExtensions,
- }
- impl<'a, 'dir> FileName<'a, 'dir> {
- /// Sets the flag on this file name to display link targets with an
- /// arrow followed by their path.
- pub fn with_link_paths(mut self) -> Self {
- self.link_style = LinkStyle::FullLinkPaths;
- self
- }
- /// 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 doesn’t need to have its
- /// width calculated.
- pub fn paint(&self) -> TextCellContents {
- let mut bits = Vec::new();
- if self.file.parent_dir.is_none() {
- if let Some(parent) = self.file.path.parent() {
- 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(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() {
- self.add_parent_bits(&mut bits, parent);
- }
- if !target.name.is_empty() {
- let target = FileName {
- file: target,
- colours: self.colours,
- target: None,
- link_style: LinkStyle::FullLinkPaths,
- classify: Classify::JustFilenames,
- exts: self.exts,
- };
- 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
- },
- }
- }
- else if let Classify::AddFileIndicators = self.classify {
- if let Some(class) = self.classify_char() {
- bits.push(Style::default().paint(class));
- }
- }
- 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) {
- 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("/"));
- }
- }
- /// 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> {
- 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
- }
- }
- /// Returns at least one ANSI-highlighted string representing this file’s
- /// name using the given set of colours.
- ///
- /// Ordinarily, this will be just one string: the file’s complete name,
- /// coloured according to its file type. If the name contains control
- /// characters such as newlines or escapes, though, we can’t just print them
- /// to the screen directly, because then there’ll 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>> {
- let file_style = self.style();
- let mut bits = Vec::new();
- escape(self.file.name.clone(), &mut bits, file_style, self.colours.control_char);
- 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 {
- // Override the style with the “broken link” style when this file is
- // a link that we can’t follow for whatever reason. This is used when
- // there’s no other place to show that the link doesn’t 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 {
- f if f.is_directory() => self.colours.filekinds.directory,
- f if f.is_executable_file() => self.colours.filekinds.executable,
- f if f.is_link() => self.colours.filekinds.symlink,
- f if f.is_pipe() => self.colours.filekinds.pipe,
- f if f.is_block_device() => self.colours.filekinds.block_device,
- f if f.is_char_device() => self.colours.filekinds.char_device,
- f if f.is_socket() => self.colours.filekinds.socket,
- f if !f.is_file() => self.colours.filekinds.special,
- f if self.exts.is_immediate(f) => self.colours.filetypes.immediate,
- f if self.exts.is_image(f) => self.colours.filetypes.image,
- f if self.exts.is_video(f) => self.colours.filetypes.video,
- f if self.exts.is_music(f) => self.colours.filetypes.music,
- f if self.exts.is_lossless(f) => self.colours.filetypes.lossless,
- f if self.exts.is_crypto(f) => self.colours.filetypes.crypto,
- f if self.exts.is_document(f) => self.colours.filetypes.document,
- f if self.exts.is_compressed(f) => self.colours.filetypes.compressed,
- f if self.exts.is_temp(f) => self.colours.filetypes.temp,
- f if self.exts.is_compiled(f) => self.colours.filetypes.compiled,
- _ => self.colours.filekinds.normal,
- }
- }
- }
|