use crate::fs::feature::VcsCache;
use crate::fs::fields::VcsStatus;
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use std::slice::Iter as SliceIter;
use log::*;
use crate::fs::File;
pub struct DirEntry {
pub path: PathBuf,
pub file_type: Option<std::fs::FileType>,
}
pub struct Dir {
contents: Vec<DirEntry>,
pub path: PathBuf,
}
impl Dir {
pub fn read_dir(path: PathBuf) -> io::Result<Self> {
info!("Reading directory {}", path.display());
let contents = fs::read_dir(&path)?
.map(|result| {
result.map(|entry| {
let file_type = entry.file_type().ok();
DirEntry {
path: entry.path(),
file_type,
}
})
})
.collect::<Result<_, _>>()?;
Ok(Self { contents, path })
}
pub fn files<'dir, 'ig>(
&'dir self,
dots: DotFilter,
git: Option<&'ig dyn VcsCache>,
git_ignoring: bool,
) -> Files<'dir, 'ig> {
Files {
inner: self.contents.iter(),
dir: self,
dotfiles: dots.shows_dotfiles(),
dots: dots.dots(),
git,
git_ignoring,
}
}
pub fn contains(&self, path: &Path) -> bool {
self.contents.iter().any(|e| e.path.as_path() == path)
}
pub fn join(&self, child: &Path) -> PathBuf {
self.path.join(child)
}
}
pub struct Files<'dir, 'ig> {
inner: SliceIter<'dir, DirEntry>,
dir: &'dir Dir,
dotfiles: bool,
dots: DotsNext,
git: Option<&'ig dyn VcsCache>,
git_ignoring: bool,
}
impl<'dir> Files<'dir, '_> {
fn parent(&self) -> PathBuf {
self.dir.path.join("..")
}
fn next_visible_file(&mut self) -> Option<Result<File<'dir>, (PathBuf, io::Error)>> {
loop {
if let Some(entry) = self.inner.next() {
let path = &entry.path;
let filename = File::filename(path);
if !self.dotfiles && filename.starts_with('.') {
continue;
}
#[cfg(windows)]
if !self.dotfiles && filename.starts_with('_') {
continue;
}
if self.git_ignoring {
let git_status = self.git.map(|g| g.get(path, false)).unwrap_or_default();
if git_status.unstaged == VcsStatus::Ignored {
continue;
}
let is_dir = entry
.file_type
.map_or_else(|| path.is_dir(), |ft| ft.is_dir());
if is_dir && is_vcs_dir(&filename) {
continue;
}
}
return Some(
File::from_args(path.clone(), self.dir, filename, entry.file_type)
.map_err(|e| (path.clone(), e)),
);
}
return None;
}
}
}
enum DotsNext {
Dot,
DotDot,
Files,
}
impl<'dir> Iterator for Files<'dir, '_> {
type Item = Result<File<'dir>, (PathBuf, io::Error)>;
fn next(&mut self) -> Option<Self::Item> {
match self.dots {
DotsNext::Dot => {
self.dots = DotsNext::DotDot;
Some(File::new_aa_current(self.dir).map_err(|e| (Path::new(".").to_path_buf(), e)))
}
DotsNext::DotDot => {
self.dots = DotsNext::Files;
Some(File::new_aa_parent(self.parent(), self.dir).map_err(|e| (self.parent(), e)))
}
DotsNext::Files => self.next_visible_file(),
}
}
}
#[derive(PartialEq, Eq, Debug, Copy, Clone, Default)]
pub struct DotFilter {
pub show_dotfiles: bool,
pub show_dot_entries: bool,
}
fn is_vcs_dir(name: &str) -> bool {
#[cfg(feature = "git")]
if name == ".git" {
return true;
}
#[cfg(feature = "jj")]
if name == ".jj" {
return true;
}
let _ = name;
false
}
impl DotFilter {
fn shows_dotfiles(self) -> bool {
self.show_dotfiles
}
fn dots(self) -> DotsNext {
if self.show_dot_entries {
DotsNext::Dot
} else {
DotsNext::Files
}
}
}