file_name.rs 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. use ansi_term::{ANSIString, Style};
  2. use fs::{File, FileTarget};
  3. use output::Colours;
  4. use output::cell::TextCellContents;
  5. pub struct FileName<'a, 'dir: 'a> {
  6. file: &'a File<'dir>,
  7. colours: &'a Colours,
  8. }
  9. impl<'a, 'dir> FileName<'a, 'dir> {
  10. pub fn new(file: &'a File<'dir>, colours: &'a Colours) -> FileName<'a, 'dir> {
  11. FileName {
  12. file: file,
  13. colours: colours,
  14. }
  15. }
  16. pub fn file_name(&self, links: bool, classify: bool) -> TextCellContents {
  17. let mut bits = Vec::new();
  18. if self.file.dir.is_none() {
  19. if let Some(parent) = self.file.path.parent() {
  20. let coconut = parent.components().count();
  21. if coconut == 1 && parent.has_root() {
  22. bits.push(self.colours.symlink_path.paint("/"));
  23. }
  24. else if coconut >= 1 {
  25. bits.push(self.colours.symlink_path.paint(parent.to_string_lossy().to_string()));
  26. bits.push(self.colours.symlink_path.paint("/"));
  27. }
  28. }
  29. }
  30. if !self.file.name.is_empty() {
  31. for bit in self.coloured_file_name() {
  32. bits.push(bit);
  33. }
  34. }
  35. if links && self.file.is_link() {
  36. match self.file.link_target() {
  37. FileTarget::Ok(target) => {
  38. bits.push(Style::default().paint(" "));
  39. bits.push(self.colours.punctuation.paint("->"));
  40. bits.push(Style::default().paint(" "));
  41. if let Some(parent) = target.path.parent() {
  42. let coconut = parent.components().count();
  43. if coconut == 1 && parent.has_root() {
  44. bits.push(self.colours.symlink_path.paint("/"));
  45. }
  46. else if coconut >= 1 {
  47. bits.push(self.colours.symlink_path.paint(parent.to_string_lossy().to_string()));
  48. bits.push(self.colours.symlink_path.paint("/"));
  49. }
  50. }
  51. if !target.name.is_empty() {
  52. let target = FileName::new(&target, self.colours);
  53. for bit in target.coloured_file_name() {
  54. bits.push(bit);
  55. }
  56. }
  57. },
  58. FileTarget::Broken(broken_path) => {
  59. bits.push(Style::default().paint(" "));
  60. bits.push(self.colours.broken_arrow.paint("->"));
  61. bits.push(Style::default().paint(" "));
  62. bits.push(self.colours.broken_filename.paint(broken_path.display().to_string()));
  63. },
  64. FileTarget::Err(_) => {
  65. // Do nothing -- the error gets displayed on the next line
  66. }
  67. }
  68. }
  69. else if classify {
  70. if let Some(class) = self.classify_char() {
  71. bits.push(Style::default().paint(class));
  72. }
  73. }
  74. bits.into()
  75. }
  76. fn classify_char(&self) -> Option<&'static str> {
  77. if self.file.is_executable_file() {
  78. Some("*")
  79. } else if self.file.is_directory() {
  80. Some("/")
  81. } else if self.file.is_pipe() {
  82. Some("|")
  83. } else if self.file.is_link() {
  84. Some("@")
  85. } else if self.file.is_socket() {
  86. Some("=")
  87. } else {
  88. None
  89. }
  90. }
  91. /// Returns at least one ANSI-highlighted string representing this file’s
  92. /// name using the given set of colours.
  93. ///
  94. /// Ordinarily, this will be just one string: the file’s complete name,
  95. /// coloured according to its file type. If the name contains control
  96. /// characters such as newlines or escapes, though, we can’t just print them
  97. /// to the screen directly, because then there’ll be newlines in weird places.
  98. ///
  99. /// So in that situation, those characters will be escaped and highlighted in
  100. /// a different colour.
  101. fn coloured_file_name<'unused>(&self) -> Vec<ANSIString<'unused>> {
  102. let file_style = self.style();
  103. let mut bits = Vec::new();
  104. if self.file.name.chars().all(|c| c >= 0x20 as char) {
  105. bits.push(file_style.paint(self.file.name.clone()));
  106. }
  107. else {
  108. for c in self.file.name.chars() {
  109. // The `escape_default` method on `char` is *almost* what we want here, but
  110. // it still escapes non-ASCII UTF-8 characters, which are still printable.
  111. if c >= 0x20 as char {
  112. // TODO: This allocates way too much,
  113. // hence the `all` check above.
  114. let mut s = String::new();
  115. s.push(c);
  116. bits.push(file_style.paint(s));
  117. } else {
  118. let s = c.escape_default().collect::<String>();
  119. bits.push(self.colours.control_char.paint(s));
  120. }
  121. }
  122. }
  123. bits
  124. }
  125. pub fn style(&self) -> Style {
  126. match self.file {
  127. f if f.is_directory() => self.colours.filetypes.directory,
  128. f if f.is_executable_file() => self.colours.filetypes.executable,
  129. f if f.is_link() => self.colours.filetypes.symlink,
  130. f if f.is_pipe() => self.colours.filetypes.pipe,
  131. f if f.is_char_device()
  132. | f.is_block_device() => self.colours.filetypes.device,
  133. f if f.is_socket() => self.colours.filetypes.socket,
  134. f if !f.is_file() => self.colours.filetypes.special,
  135. f if f.is_immediate() => self.colours.filetypes.immediate,
  136. f if f.is_image() => self.colours.filetypes.image,
  137. f if f.is_video() => self.colours.filetypes.video,
  138. f if f.is_music() => self.colours.filetypes.music,
  139. f if f.is_lossless() => self.colours.filetypes.lossless,
  140. f if f.is_crypto() => self.colours.filetypes.crypto,
  141. f if f.is_document() => self.colours.filetypes.document,
  142. f if f.is_compressed() => self.colours.filetypes.compressed,
  143. f if f.is_temp() => self.colours.filetypes.temp,
  144. f if f.is_compiled() => self.colours.filetypes.compiled,
  145. _ => self.colours.filetypes.normal,
  146. }
  147. }
  148. }