diff --git a/src/output/details.rs b/src/output/details.rs index f182caa..ed5f5c0 100644 --- a/src/output/details.rs +++ b/src/output/details.rs @@ -101,7 +101,7 @@ use output::colours::Colours; use output::column::{Alignment, Column, Columns, SizeFormat}; use output::cell::{TextCell, DisplayWidth}; use output::tree::TreeTrunk; -use output::file_name::filename; +use output::file_name::FileName; /// With the **Details** view, the output gets formatted into columns, with @@ -307,7 +307,7 @@ impl Details { let mut files = Vec::new(); let mut errors = egg.errors; - let filename = filename(&egg.file, &self.colours, true, self.classify); + let filename = FileName::new(&egg.file, &self.colours).file_name(true, self.classify); let mut width = filename.width(); if egg.file.dir.is_none() { @@ -458,7 +458,7 @@ impl<'a, U: Users+Groups+'a> Table<'a, U> { } pub fn filename_cell(&self, file: File, links: bool) -> TextCell { - let filename = filename(&file, &self.opts.colours, links, self.opts.classify); + let filename = FileName::new(&file, &self.opts.colours).file_name(links, self.opts.classify); let mut width = filename.width(); if file.dir.is_none() { diff --git a/src/output/file_name.rs b/src/output/file_name.rs index 4487799..e32fad0 100644 --- a/src/output/file_name.rs +++ b/src/output/file_name.rs @@ -5,141 +5,153 @@ use output::Colours; use output::cell::TextCellContents; -pub fn filename(file: &File, colours: &Colours, links: bool, classify: bool) -> TextCellContents { - let mut bits = Vec::new(); +pub struct FileName<'a, 'dir: 'a> { + file: &'a File<'dir>, + colours: &'a Colours, +} - // TODO: This long function could do with some splitting up. - - if file.dir.is_none() { - if let Some(parent) = file.path.parent() { - let coconut = parent.components().count(); - - if coconut == 1 && parent.has_root() { - bits.push(colours.symlink_path.paint("/")); - } - else if coconut >= 1 { - bits.push(colours.symlink_path.paint(parent.to_string_lossy().to_string())); - bits.push(colours.symlink_path.paint("/")); - } +impl<'a, 'dir> FileName<'a, 'dir> { + pub fn new(file: &'a File<'dir>, colours: &'a Colours) -> FileName<'a, 'dir> { + FileName { + file: file, + colours: colours, } } - if !file.name.is_empty() { - for bit in coloured_file_name(file, colours) { - bits.push(bit); - } - } + pub fn file_name(&self, links: bool, classify: bool) -> TextCellContents { + let mut bits = Vec::new(); - if links && file.is_link() { - match file.link_target() { - FileTarget::Ok(target) => { - bits.push(Style::default().paint(" ")); - bits.push(colours.punctuation.paint("->")); - bits.push(Style::default().paint(" ")); + if self.file.dir.is_none() { + if let Some(parent) = self.file.path.parent() { + let coconut = parent.components().count(); - if let Some(parent) = target.path.parent() { - let coconut = parent.components().count(); - - if coconut == 1 && parent.has_root() { - bits.push(colours.symlink_path.paint("/")); - } - else if coconut >= 1 { - bits.push(colours.symlink_path.paint(parent.to_string_lossy().to_string())); - bits.push(colours.symlink_path.paint("/")); - } + if coconut == 1 && parent.has_root() { + bits.push(self.colours.symlink_path.paint("/")); } - - if !target.name.is_empty() { - bits.push(file_colour(colours, &target).paint(target.name)); + else if coconut >= 1 { + bits.push(self.colours.symlink_path.paint(parent.to_string_lossy().to_string())); + bits.push(self.colours.symlink_path.paint("/")); } - }, - - FileTarget::Broken(broken_path) => { - bits.push(Style::default().paint(" ")); - bits.push(colours.broken_arrow.paint("->")); - bits.push(Style::default().paint(" ")); - bits.push(colours.broken_filename.paint(broken_path.display().to_string())); - }, - - FileTarget::Err(_) => { - // Do nothing -- the error gets displayed on the next line } } - } else if classify { - if file.is_executable_file() { - bits.push(Style::default().paint("*")); - } else if file.is_directory() { - bits.push(Style::default().paint("/")); - } else if file.is_pipe() { - bits.push(Style::default().paint("|")); - } else if file.is_link() { - bits.push(Style::default().paint("@")); - } else if file.is_socket() { - bits.push(Style::default().paint("=")); - } - } - bits.into() -} - -/// 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<'a>(file: &File, colours: &Colours) -> Vec> { - let colour = file_colour(colours, file); - let mut bits = Vec::new(); - - if file.name.chars().all(|c| c >= 0x20 as char) { - bits.push(colour.paint(file.name.clone())); - } - else { - for c in file.name.chars() { - // The `escape_default` method on `char` is *almost* what we want here, but - // it still escapes non-ASCII UTF-8 characters, which are still printable. - - if c >= 0x20 as char { - // TODO: This allocates way too much, - // hence the `all` check above. - let mut s = String::new(); - s.push(c); - bits.push(colour.paint(s)); - } else { - let s = c.escape_default().collect::(); - bits.push(colours.control_char.paint(s)); + if !self.file.name.is_empty() { + for bit in self.coloured_file_name() { + bits.push(bit); } } + + if links && self.file.is_link() { + match self.file.link_target() { + FileTarget::Ok(target) => { + bits.push(Style::default().paint(" ")); + bits.push(self.colours.punctuation.paint("->")); + bits.push(Style::default().paint(" ")); + + if let Some(parent) = target.path.parent() { + let coconut = parent.components().count(); + + if coconut == 1 && parent.has_root() { + bits.push(self.colours.symlink_path.paint("/")); + } + else if coconut >= 1 { + bits.push(self.colours.symlink_path.paint(parent.to_string_lossy().to_string())); + bits.push(self.colours.symlink_path.paint("/")); + } + } + + if !target.name.is_empty() { + bits.push(FileName::new(&target, self.colours).file_colour().paint(target.name)); + } + }, + + FileTarget::Broken(broken_path) => { + bits.push(Style::default().paint(" ")); + bits.push(self.colours.broken_arrow.paint("->")); + bits.push(Style::default().paint(" ")); + bits.push(self.colours.broken_filename.paint(broken_path.display().to_string())); + }, + + FileTarget::Err(_) => { + // Do nothing -- the error gets displayed on the next line + } + } + } else if classify { + if self.file.is_executable_file() { + bits.push(Style::default().paint("*")); + } else if self.file.is_directory() { + bits.push(Style::default().paint("/")); + } else if self.file.is_pipe() { + bits.push(Style::default().paint("|")); + } else if self.file.is_link() { + bits.push(Style::default().paint("@")); + } else if self.file.is_socket() { + bits.push(Style::default().paint("=")); + } + } + + bits.into() } - bits -} + /// 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> { + let colour = self.file_colour(); + let mut bits = Vec::new(); -pub fn file_colour(colours: &Colours, file: &File) -> Style { - match file { - f if f.is_directory() => colours.filetypes.directory, - f if f.is_executable_file() => colours.filetypes.executable, - f if f.is_link() => colours.filetypes.symlink, - f if f.is_pipe() => colours.filetypes.pipe, - f if f.is_char_device() - | f.is_block_device() => colours.filetypes.device, - f if f.is_socket() => colours.filetypes.socket, - f if !f.is_file() => colours.filetypes.special, - f if f.is_immediate() => colours.filetypes.immediate, - f if f.is_image() => colours.filetypes.image, - f if f.is_video() => colours.filetypes.video, - f if f.is_music() => colours.filetypes.music, - f if f.is_lossless() => colours.filetypes.lossless, - f if f.is_crypto() => colours.filetypes.crypto, - f if f.is_document() => colours.filetypes.document, - f if f.is_compressed() => colours.filetypes.compressed, - f if f.is_temp() => colours.filetypes.temp, - f if f.is_compiled() => colours.filetypes.compiled, - _ => colours.filetypes.normal, + if self.file.name.chars().all(|c| c >= 0x20 as char) { + bits.push(colour.paint(self.file.name.clone())); + } + else { + for c in self.file.name.chars() { + // The `escape_default` method on `char` is *almost* what we want here, but + // it still escapes non-ASCII UTF-8 characters, which are still printable. + + if c >= 0x20 as char { + // TODO: This allocates way too much, + // hence the `all` check above. + let mut s = String::new(); + s.push(c); + bits.push(colour.paint(s)); + } else { + let s = c.escape_default().collect::(); + bits.push(self.colours.control_char.paint(s)); + } + } + } + + bits + } + + pub fn file_colour(&self) -> Style { + 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, + } } } diff --git a/src/output/grid.rs b/src/output/grid.rs index 9a87580..3351577 100644 --- a/src/output/grid.rs +++ b/src/output/grid.rs @@ -5,7 +5,7 @@ use term_grid as grid; use fs::File; use output::DisplayWidth; use output::colours::Colours; -use output::file_name::filename; +use output::file_name::FileName; #[derive(PartialEq, Debug, Copy, Clone)] @@ -29,7 +29,7 @@ impl Grid { grid.reserve(files.len()); for file in files.iter() { - let filename = filename(file, &self.colours, false, self.classify); + let filename = FileName::new(file, &self.colours).file_name(false, self.classify); let mut width = filename.width(); if file.dir.is_none() { @@ -50,7 +50,7 @@ impl Grid { else { // File names too long for a grid - drop down to just listing them! for file in files.iter() { - writeln!(w, "{}", filename(file, &self.colours, false, self.classify).strings())?; + writeln!(w, "{}", FileName::new(file, &self.colours).file_name(false, self.classify).strings())?; } Ok(()) } diff --git a/src/output/lines.rs b/src/output/lines.rs index e7512ad..358f05f 100644 --- a/src/output/lines.rs +++ b/src/output/lines.rs @@ -4,7 +4,7 @@ use ansi_term::ANSIStrings; use fs::File; -use output::file_name::filename; +use output::file_name::FileName; use super::colours::Colours; @@ -18,7 +18,7 @@ pub struct Lines { impl Lines { pub fn view(&self, files: Vec, w: &mut W) -> IOResult<()> { for file in files { - writeln!(w, "{}", ANSIStrings(&filename(&file, &self.colours, true, self.classify)))?; + writeln!(w, "{}", ANSIStrings(&FileName::new(&file, &self.colours).file_name(true, self.classify)))?; } Ok(()) }