dir.rs 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. use crate::fs::feature::git::GitCache;
  2. use crate::fs::fields::GitStatus;
  3. use std::io;
  4. use std::fs;
  5. use std::path::{Path, PathBuf};
  6. use std::slice::Iter as SliceIter;
  7. use log::*;
  8. use crate::fs::File;
  9. /// A **Dir** provides a cached list of the file paths in a directory that’s
  10. /// being listed.
  11. ///
  12. /// This object gets passed to the Files themselves, in order for them to
  13. /// check the existence of surrounding files, then highlight themselves
  14. /// accordingly. (See `File#get_source_files`)
  15. pub struct Dir {
  16. /// A vector of the files that have been read from this directory.
  17. contents: Vec<PathBuf>,
  18. /// The path that was read.
  19. pub path: PathBuf,
  20. }
  21. impl Dir {
  22. /// Create a new Dir object filled with all the files in the directory
  23. /// pointed to by the given path. Fails if the directory can’t be read, or
  24. /// isn’t actually a directory, or if there’s an IO error that occurs at
  25. /// any point.
  26. ///
  27. /// The `read_dir` iterator doesn’t actually yield the `.` and `..`
  28. /// entries, so if the user wants to see them, we’ll have to add them
  29. /// ourselves after the files have been read.
  30. pub fn read_dir(path: PathBuf) -> io::Result<Self> {
  31. info!("Reading directory {:?}", &path);
  32. let contents = fs::read_dir(&path)?
  33. .map(|result| result.map(|entry| entry.path()))
  34. .collect::<Result<_, _>>()?;
  35. Ok(Self { contents, path })
  36. }
  37. /// Produce an iterator of IO results of trying to read all the files in
  38. /// this directory.
  39. pub fn files<'dir, 'ig>(&'dir self, dots: DotFilter, git: Option<&'ig GitCache>, git_ignoring: bool) -> Files<'dir, 'ig> {
  40. Files {
  41. inner: self.contents.iter(),
  42. dir: self,
  43. dotfiles: dots.shows_dotfiles(),
  44. dots: dots.dots(),
  45. git,
  46. git_ignoring,
  47. }
  48. }
  49. /// Whether this directory contains a file with the given path.
  50. pub fn contains(&self, path: &Path) -> bool {
  51. self.contents.iter().any(|p| p.as_path() == path)
  52. }
  53. /// Append a path onto the path specified by this directory.
  54. pub fn join(&self, child: &Path) -> PathBuf {
  55. self.path.join(child)
  56. }
  57. }
  58. /// Iterator over reading the contents of a directory as `File` objects.
  59. pub struct Files<'dir, 'ig> {
  60. /// The internal iterator over the paths that have been read already.
  61. inner: SliceIter<'dir, PathBuf>,
  62. /// The directory that begat those paths.
  63. dir: &'dir Dir,
  64. /// Whether to include dotfiles in the list.
  65. dotfiles: bool,
  66. /// Whether the `.` or `..` directories should be produced first, before
  67. /// any files have been listed.
  68. dots: DotsNext,
  69. git: Option<&'ig GitCache>,
  70. git_ignoring: bool,
  71. }
  72. impl<'dir, 'ig> Files<'dir, 'ig> {
  73. fn parent(&self) -> PathBuf {
  74. // We can’t use `Path#parent` here because all it does is remove the
  75. // last path component, which is no good for us if the path is
  76. // relative. For example, while the parent of `/testcases/files` is
  77. // `/testcases`, the parent of `.` is an empty path. Adding `..` on
  78. // the end is the only way to get to the *actual* parent directory.
  79. self.dir.path.join("..")
  80. }
  81. /// Go through the directory until we encounter a file we can list (which
  82. /// varies depending on the dotfile visibility flag)
  83. fn next_visible_file(&mut self) -> Option<Result<File<'dir>, (PathBuf, io::Error)>> {
  84. loop {
  85. if let Some(path) = self.inner.next() {
  86. let filename = File::filename(path);
  87. if ! self.dotfiles && filename.starts_with('.') {
  88. continue;
  89. }
  90. if self.git_ignoring {
  91. let git_status = self.git.map(|g| g.get(path, false)).unwrap_or_default();
  92. if git_status.unstaged == GitStatus::Ignored {
  93. continue;
  94. }
  95. }
  96. return Some(File::from_args(path.clone(), self.dir, filename)
  97. .map_err(|e| (path.clone(), e)))
  98. }
  99. return None
  100. }
  101. }
  102. }
  103. /// The dot directories that need to be listed before actual files, if any.
  104. /// If these aren’t being printed, then `FilesNext` is used to skip them.
  105. enum DotsNext {
  106. /// List the `.` directory next.
  107. Dot,
  108. /// List the `..` directory next.
  109. DotDot,
  110. /// Forget about the dot directories and just list files.
  111. Files,
  112. }
  113. impl<'dir, 'ig> Iterator for Files<'dir, 'ig> {
  114. type Item = Result<File<'dir>, (PathBuf, io::Error)>;
  115. fn next(&mut self) -> Option<Self::Item> {
  116. match self.dots {
  117. DotsNext::Dot => {
  118. self.dots = DotsNext::DotDot;
  119. Some(File::new_aa_current(self.dir)
  120. .map_err(|e| (Path::new(".").to_path_buf(), e)))
  121. }
  122. DotsNext::DotDot => {
  123. self.dots = DotsNext::Files;
  124. Some(File::new_aa_parent(self.parent(), self.dir)
  125. .map_err(|e| (self.parent(), e)))
  126. }
  127. DotsNext::Files => {
  128. self.next_visible_file()
  129. }
  130. }
  131. }
  132. }
  133. /// Usually files in Unix use a leading dot to be hidden or visible, but two
  134. /// entries in particular are “extra-hidden”: `.` and `..`, which only become
  135. /// visible after an extra `-a` option.
  136. #[derive(PartialEq, Debug, Copy, Clone)]
  137. pub enum DotFilter {
  138. /// Shows files, dotfiles, and `.` and `..`.
  139. DotfilesAndDots,
  140. /// Show files and dotfiles, but hide `.` and `..`.
  141. Dotfiles,
  142. /// Just show files, hiding anything beginning with a dot.
  143. JustFiles,
  144. }
  145. impl Default for DotFilter {
  146. fn default() -> Self {
  147. Self::JustFiles
  148. }
  149. }
  150. impl DotFilter {
  151. /// Whether this filter should show dotfiles in a listing.
  152. fn shows_dotfiles(self) -> bool {
  153. match self {
  154. Self::JustFiles => false,
  155. Self::Dotfiles => true,
  156. Self::DotfilesAndDots => true,
  157. }
  158. }
  159. /// Whether this filter should add dot directories to a listing.
  160. fn dots(self) -> DotsNext {
  161. match self {
  162. Self::JustFiles => DotsNext::Files,
  163. Self::Dotfiles => DotsNext::Files,
  164. Self::DotfilesAndDots => DotsNext::Dot,
  165. }
  166. }
  167. }