file_name.rs 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. use std::path::Path;
  2. use ansi_term::{ANSIString, Style};
  3. use fs::{File, FileTarget};
  4. use info::filetype::FileExtensions;
  5. use output::Colours;
  6. use output::escape;
  7. use output::cell::TextCellContents;
  8. /// Basically a file name factory.
  9. #[derive(Debug)]
  10. pub struct FileStyle {
  11. /// Whether to append file class characters to file names.
  12. pub classify: Classify,
  13. /// Mapping of file extensions to colours, to highlight regular files.
  14. pub exts: FileExtensions,
  15. }
  16. impl FileStyle {
  17. /// Create a new `FileName` that prints the given file’s name, painting it
  18. /// with the remaining arguments.
  19. pub fn for_file<'a, 'dir>(&'a self, file: &'a File<'dir>, colours: &'a Colours) -> FileName<'a, 'dir> {
  20. FileName {
  21. file, colours,
  22. link_style: LinkStyle::JustFilenames,
  23. exts: &self.exts,
  24. classify: self.classify,
  25. target: if file.is_link() { Some(file.link_target()) }
  26. else { None }
  27. }
  28. }
  29. }
  30. /// When displaying a file name, there needs to be some way to handle broken
  31. /// links, depending on how long the resulting Cell can be.
  32. #[derive(PartialEq, Debug, Copy, Clone)]
  33. enum LinkStyle {
  34. /// Just display the file names, but colour them differently if they’re
  35. /// a broken link or can’t be followed.
  36. JustFilenames,
  37. /// Display all files in their usual style, but follow each link with an
  38. /// arrow pointing to their path, colouring the path differently if it’s
  39. /// a broken link, and doing nothing if it can’t be followed.
  40. FullLinkPaths,
  41. }
  42. /// Whether to append file class characters to the file names.
  43. #[derive(PartialEq, Debug, Copy, Clone)]
  44. pub enum Classify {
  45. /// Just display the file names, without any characters.
  46. JustFilenames,
  47. /// Add a character after the file name depending on what class of file
  48. /// it is.
  49. AddFileIndicators,
  50. }
  51. impl Default for Classify {
  52. fn default() -> Classify {
  53. Classify::JustFilenames
  54. }
  55. }
  56. /// A **file name** holds all the information necessary to display the name
  57. /// of the given file. This is used in all of the views.
  58. pub struct FileName<'a, 'dir: 'a> {
  59. /// A reference to the file that we're getting the name of.
  60. file: &'a File<'dir>,
  61. /// The colours used to paint the file name and its surrounding text.
  62. colours: &'a Colours,
  63. /// The file that this file points to if it's a link.
  64. target: Option<FileTarget<'dir>>,
  65. /// How to handle displaying links.
  66. link_style: LinkStyle,
  67. /// Whether to append file class characters to file names.
  68. classify: Classify,
  69. /// Mapping of file extensions to colours, to highlight regular files.
  70. exts: &'a FileExtensions,
  71. }
  72. impl<'a, 'dir> FileName<'a, 'dir> {
  73. /// Sets the flag on this file name to display link targets with an
  74. /// arrow followed by their path.
  75. pub fn with_link_paths(mut self) -> Self {
  76. self.link_style = LinkStyle::FullLinkPaths;
  77. self
  78. }
  79. /// Paints the name of the file using the colours, resulting in a vector
  80. /// of coloured cells that can be printed to the terminal.
  81. ///
  82. /// This method returns some `TextCellContents`, rather than a `TextCell`,
  83. /// because for the last cell in a table, it doesn’t need to have its
  84. /// width calculated.
  85. pub fn paint(&self) -> TextCellContents {
  86. let mut bits = Vec::new();
  87. if self.file.parent_dir.is_none() {
  88. if let Some(parent) = self.file.path.parent() {
  89. self.add_parent_bits(&mut bits, parent);
  90. }
  91. }
  92. if !self.file.name.is_empty() {
  93. for bit in self.coloured_file_name() {
  94. bits.push(bit);
  95. }
  96. }
  97. if let (LinkStyle::FullLinkPaths, Some(target)) = (self.link_style, self.target.as_ref()) {
  98. match *target {
  99. FileTarget::Ok(ref target) => {
  100. bits.push(Style::default().paint(" "));
  101. bits.push(self.colours.punctuation.paint("->"));
  102. bits.push(Style::default().paint(" "));
  103. if let Some(parent) = target.path.parent() {
  104. self.add_parent_bits(&mut bits, parent);
  105. }
  106. if !target.name.is_empty() {
  107. let target = FileName {
  108. file: target,
  109. colours: self.colours,
  110. target: None,
  111. link_style: LinkStyle::FullLinkPaths,
  112. classify: Classify::JustFilenames,
  113. exts: self.exts,
  114. };
  115. for bit in target.coloured_file_name() {
  116. bits.push(bit);
  117. }
  118. }
  119. },
  120. FileTarget::Broken(ref broken_path) => {
  121. bits.push(Style::default().paint(" "));
  122. bits.push(self.colours.broken_arrow.paint("->"));
  123. bits.push(Style::default().paint(" "));
  124. escape(broken_path.display().to_string(), &mut bits, self.colours.broken_filename, self.colours.control_char.underline());
  125. },
  126. FileTarget::Err(_) => {
  127. // Do nothing -- the error gets displayed on the next line
  128. },
  129. }
  130. }
  131. else if let Classify::AddFileIndicators = self.classify {
  132. if let Some(class) = self.classify_char() {
  133. bits.push(Style::default().paint(class));
  134. }
  135. }
  136. bits.into()
  137. }
  138. /// Adds the bits of the parent path to the given bits vector.
  139. /// The path gets its characters escaped based on the colours.
  140. fn add_parent_bits(&self, bits: &mut Vec<ANSIString>, parent: &Path) {
  141. let coconut = parent.components().count();
  142. if coconut == 1 && parent.has_root() {
  143. bits.push(self.colours.symlink_path.paint("/"));
  144. }
  145. else if coconut >= 1 {
  146. escape(parent.to_string_lossy().to_string(), bits, self.colours.symlink_path, self.colours.control_char);
  147. bits.push(self.colours.symlink_path.paint("/"));
  148. }
  149. }
  150. /// The character to be displayed after a file when classifying is on, if
  151. /// the file’s type has one associated with it.
  152. fn classify_char(&self) -> Option<&'static str> {
  153. if self.file.is_executable_file() {
  154. Some("*")
  155. } else if self.file.is_directory() {
  156. Some("/")
  157. } else if self.file.is_pipe() {
  158. Some("|")
  159. } else if self.file.is_link() {
  160. Some("@")
  161. } else if self.file.is_socket() {
  162. Some("=")
  163. } else {
  164. None
  165. }
  166. }
  167. /// Returns at least one ANSI-highlighted string representing this file’s
  168. /// name using the given set of colours.
  169. ///
  170. /// Ordinarily, this will be just one string: the file’s complete name,
  171. /// coloured according to its file type. If the name contains control
  172. /// characters such as newlines or escapes, though, we can’t just print them
  173. /// to the screen directly, because then there’ll be newlines in weird places.
  174. ///
  175. /// So in that situation, those characters will be escaped and highlighted in
  176. /// a different colour.
  177. fn coloured_file_name<'unused>(&self) -> Vec<ANSIString<'unused>> {
  178. let file_style = self.style();
  179. let mut bits = Vec::new();
  180. escape(self.file.name.clone(), &mut bits, file_style, self.colours.control_char);
  181. bits
  182. }
  183. /// Figures out which colour to paint the filename part of the output,
  184. /// depending on which “type” of file it appears to be -- either from the
  185. /// class on the filesystem or from its name.
  186. pub fn style(&self) -> Style {
  187. // Override the style with the “broken link” style when this file is
  188. // a link that we can’t follow for whatever reason. This is used when
  189. // there’s no other place to show that the link doesn’t work.
  190. if let LinkStyle::JustFilenames = self.link_style {
  191. if let Some(ref target) = self.target {
  192. if target.is_broken() {
  193. return self.colours.broken_arrow;
  194. }
  195. }
  196. }
  197. // Otherwise, just apply a bunch of rules in order. For example,
  198. // executable image files should be executable rather than images.
  199. match self.file {
  200. f if f.is_directory() => self.colours.filekinds.directory,
  201. f if f.is_executable_file() => self.colours.filekinds.executable,
  202. f if f.is_link() => self.colours.filekinds.symlink,
  203. f if f.is_pipe() => self.colours.filekinds.pipe,
  204. f if f.is_block_device() => self.colours.filekinds.block_device,
  205. f if f.is_char_device() => self.colours.filekinds.char_device,
  206. f if f.is_socket() => self.colours.filekinds.socket,
  207. f if !f.is_file() => self.colours.filekinds.special,
  208. f if self.exts.is_immediate(f) => self.colours.filetypes.immediate,
  209. f if self.exts.is_image(f) => self.colours.filetypes.image,
  210. f if self.exts.is_video(f) => self.colours.filetypes.video,
  211. f if self.exts.is_music(f) => self.colours.filetypes.music,
  212. f if self.exts.is_lossless(f) => self.colours.filetypes.lossless,
  213. f if self.exts.is_crypto(f) => self.colours.filetypes.crypto,
  214. f if self.exts.is_document(f) => self.colours.filetypes.document,
  215. f if self.exts.is_compressed(f) => self.colours.filetypes.compressed,
  216. f if self.exts.is_temp(f) => self.colours.filetypes.temp,
  217. f if self.exts.is_compiled(f) => self.colours.filetypes.compiled,
  218. _ => self.colours.filekinds.normal,
  219. }
  220. }
  221. }