mirror of
https://github.com/Llewellynvdm/exa.git
synced 2025-01-24 14:18:27 +00:00
Initial tree implementation
There's still a lot to do, but this is actually *something*. The tree hierarchy is displayed using hashes at the start of a line. I want to have it just before the filename, but this will need some changes to the way that columns are handled.
This commit is contained in:
parent
7acc1b09d5
commit
5099b3f119
@ -31,11 +31,14 @@ impl Dir {
|
||||
|
||||
/// Produce a vector of File objects from an initialised directory,
|
||||
/// printing out an error if any of the Files fail to be created.
|
||||
pub fn files(&self) -> Vec<File> {
|
||||
///
|
||||
/// Passing in `recurse` means that any directories will be scanned for
|
||||
/// their contents, as well.
|
||||
pub fn files(&self, recurse: bool) -> Vec<File> {
|
||||
let mut files = vec![];
|
||||
|
||||
for path in self.contents.iter() {
|
||||
match File::from_path(path, Some(self)) {
|
||||
match File::from_path(path, Some(self), recurse) {
|
||||
Ok(file) => files.push(file),
|
||||
Err(e) => println!("{}: {}", path.display(), e),
|
||||
}
|
||||
|
19
src/file.rs
19
src/file.rs
@ -32,6 +32,7 @@ pub struct File<'a> {
|
||||
pub ext: Option<String>,
|
||||
pub path: Path,
|
||||
pub stat: io::FileStat,
|
||||
pub this: Option<Dir>,
|
||||
}
|
||||
|
||||
impl<'a> File<'a> {
|
||||
@ -39,12 +40,12 @@ impl<'a> File<'a> {
|
||||
/// appropriate. Paths specified directly on the command-line have no Dirs.
|
||||
///
|
||||
/// This uses lstat instead of stat, which doesn't follow symbolic links.
|
||||
pub fn from_path(path: &Path, parent: Option<&'a Dir>) -> IoResult<File<'a>> {
|
||||
fs::lstat(path).map(|stat| File::with_stat(stat, path, parent))
|
||||
pub fn from_path(path: &Path, parent: Option<&'a Dir>, recurse: bool) -> IoResult<File<'a>> {
|
||||
fs::lstat(path).map(|stat| File::with_stat(stat, path, parent, recurse))
|
||||
}
|
||||
|
||||
/// Create a new File object from the given Stat result, and other data.
|
||||
pub fn with_stat(stat: io::FileStat, path: &Path, parent: Option<&'a Dir>) -> File<'a> {
|
||||
pub fn with_stat(stat: io::FileStat, path: &Path, parent: Option<&'a Dir>, recurse: bool) -> File<'a> {
|
||||
|
||||
// The filename to display is the last component of the path. However,
|
||||
// the path has no components for `.`, `..`, and `/`, so in these
|
||||
@ -58,12 +59,23 @@ impl<'a> File<'a> {
|
||||
// replacement characters.
|
||||
let filename = String::from_utf8_lossy(bytes);
|
||||
|
||||
// If we are recursing, then the `this` field contains a Dir object
|
||||
// that represents the current File as a directory, if it is a
|
||||
// directory. This is used for the --tree option.
|
||||
let this = if recurse && stat.kind == io::FileType::Directory {
|
||||
Dir::readdir(path).ok()
|
||||
}
|
||||
else {
|
||||
None
|
||||
};
|
||||
|
||||
File {
|
||||
path: path.clone(),
|
||||
dir: parent,
|
||||
stat: stat,
|
||||
name: filename.to_string(),
|
||||
ext: ext(filename.as_slice()),
|
||||
this: this,
|
||||
}
|
||||
}
|
||||
|
||||
@ -184,6 +196,7 @@ impl<'a> File<'a> {
|
||||
stat: stat,
|
||||
name: filename.to_string(),
|
||||
ext: ext(filename.as_slice()),
|
||||
this: None,
|
||||
})
|
||||
}
|
||||
else {
|
||||
|
@ -39,11 +39,14 @@ fn exa(options: &Options) {
|
||||
let path = Path::new(file);
|
||||
match fs::stat(&path) {
|
||||
Ok(stat) => {
|
||||
if stat.kind == FileType::Directory && options.dir_action != DirAction::AsFile {
|
||||
if stat.kind == FileType::Directory && options.dir_action == DirAction::Tree {
|
||||
files.push(File::with_stat(stat, &path, None, true));
|
||||
}
|
||||
else if stat.kind == FileType::Directory && options.dir_action != DirAction::AsFile {
|
||||
dirs.push(path);
|
||||
}
|
||||
else {
|
||||
files.push(File::with_stat(stat, &path, None));
|
||||
files.push(File::with_stat(stat, &path, None, false));
|
||||
}
|
||||
}
|
||||
Err(e) => println!("{}: {}", file, e),
|
||||
@ -77,7 +80,7 @@ fn exa(options: &Options) {
|
||||
|
||||
match Dir::readdir(&dir_path) {
|
||||
Ok(ref dir) => {
|
||||
let unsorted_files = dir.files();
|
||||
let unsorted_files = dir.files(false);
|
||||
let files: Vec<File> = options.transform_files(unsorted_files);
|
||||
|
||||
// When recursing, add any directories to the dirs stack
|
||||
|
@ -45,6 +45,7 @@ impl Options {
|
||||
getopts::optflag("R", "recurse", "recurse into directories"),
|
||||
getopts::optopt ("s", "sort", "field to sort by", "WORD"),
|
||||
getopts::optflag("S", "blocks", "show number of file system blocks"),
|
||||
getopts::optflag("T", "tree", "recurse into subdirectories in a tree view"),
|
||||
getopts::optflag("x", "across", "sort multi-column view entries across"),
|
||||
getopts::optflag("?", "help", "show list of command-line options"),
|
||||
];
|
||||
@ -111,7 +112,7 @@ impl Options {
|
||||
/// What to do when encountering a directory?
|
||||
#[derive(PartialEq, Debug, Copy)]
|
||||
pub enum DirAction {
|
||||
AsFile, List, Recurse
|
||||
AsFile, List, Recurse, Tree
|
||||
}
|
||||
|
||||
/// User-supplied field to sort by.
|
||||
@ -189,7 +190,7 @@ fn view(matches: &getopts::Matches) -> Result<View, Misfire> {
|
||||
Err(Misfire::Useless("oneline", true, "long"))
|
||||
}
|
||||
else {
|
||||
Ok(View::Details(try!(Columns::new(matches)), matches.opt_present("header")))
|
||||
Ok(View::Details(try!(Columns::new(matches)), matches.opt_present("header"), matches.opt_present("tree")))
|
||||
}
|
||||
}
|
||||
else if matches.opt_present("binary") {
|
||||
@ -242,12 +243,14 @@ fn file_size(matches: &getopts::Matches) -> Result<SizeFormat, Misfire> {
|
||||
fn dir_action(matches: &getopts::Matches) -> Result<DirAction, Misfire> {
|
||||
let recurse = matches.opt_present("recurse");
|
||||
let list = matches.opt_present("list-dirs");
|
||||
let tree = matches.opt_present("tree");
|
||||
|
||||
match (recurse, list) {
|
||||
(true, true ) => Err(Misfire::Conflict("recurse", "list-dirs")),
|
||||
(true, false) => Ok(DirAction::Recurse),
|
||||
(false, true ) => Ok(DirAction::AsFile),
|
||||
(false, false) => Ok(DirAction::List),
|
||||
match (recurse, list, tree) {
|
||||
(true, true, _ ) => Err(Misfire::Conflict("recurse", "list-dirs")),
|
||||
(true, false, false) => Ok(DirAction::Recurse),
|
||||
(true, false, true ) => Ok(DirAction::Tree),
|
||||
(false, true, _ ) => Ok(DirAction::AsFile),
|
||||
(false, false, _ ) => Ok(DirAction::List),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,7 @@ use ansi_term::Style::Plain;
|
||||
|
||||
#[derive(PartialEq, Copy, Debug)]
|
||||
pub enum View {
|
||||
Details(Columns, bool),
|
||||
Details(Columns, bool, bool),
|
||||
Lines,
|
||||
Grid(bool, usize),
|
||||
}
|
||||
@ -21,7 +21,7 @@ impl View {
|
||||
pub fn view(&self, dir: Option<&Dir>, files: &[File]) {
|
||||
match *self {
|
||||
View::Grid(across, width) => grid_view(across, width, files),
|
||||
View::Details(ref cols, header) => details_view(&*cols.for_dir(dir), files, header),
|
||||
View::Details(ref cols, header, tree) => details_view(&*cols.for_dir(dir), files, header, tree),
|
||||
View::Lines => lines_view(files),
|
||||
}
|
||||
}
|
||||
@ -122,7 +122,7 @@ fn grid_view(across: bool, console_width: usize, files: &[File]) {
|
||||
}
|
||||
}
|
||||
|
||||
fn details_view(columns: &[Column], files: &[File], header: bool) {
|
||||
fn details_view(columns: &[Column], files: &[File], header: bool, tree: bool) {
|
||||
// The output gets formatted into columns, which looks nicer. To
|
||||
// do this, we have to write the results into a table, instead of
|
||||
// displaying each file immediately, then calculating the maximum
|
||||
@ -131,19 +131,22 @@ fn details_view(columns: &[Column], files: &[File], header: bool) {
|
||||
|
||||
let mut cache = OSUsers::empty_cache();
|
||||
|
||||
let mut table: Vec<Vec<Cell>> = files.iter()
|
||||
.map(|f| columns.iter().map(|c| f.display(c, &mut cache)).collect())
|
||||
.collect();
|
||||
let mut table = Vec::new();
|
||||
get_files(columns, &mut cache, tree, &mut table, files, 0);
|
||||
|
||||
if header {
|
||||
table.insert(0, columns.iter().map(|c| Cell::paint(Plain.underline(), c.header())).collect());
|
||||
table.insert(0, (0, columns.iter().map(|c| Cell::paint(Plain.underline(), c.header())).collect()));
|
||||
}
|
||||
|
||||
let column_widths: Vec<usize> = range(0, columns.len())
|
||||
.map(|n| table.iter().map(|row| row[n].length).max().unwrap_or(0))
|
||||
.map(|n| table.iter().map(|row| row.1[n].length).max().unwrap_or(0))
|
||||
.collect();
|
||||
|
||||
for row in table.iter() {
|
||||
for &(depth, ref row) in table.iter() {
|
||||
for _ in range(0, depth) {
|
||||
print!("#");
|
||||
}
|
||||
|
||||
for (num, column) in columns.iter().enumerate() {
|
||||
if num != 0 {
|
||||
print!(" "); // Separator
|
||||
@ -161,3 +164,16 @@ fn details_view(columns: &[Column], files: &[File], header: bool) {
|
||||
print!("\n");
|
||||
}
|
||||
}
|
||||
|
||||
fn get_files(columns: &[Column], cache: &mut OSUsers, recurse: bool, dest: &mut Vec<(usize, Vec<Cell>)>, src: &[File], depth: usize) {
|
||||
for file in src.iter() {
|
||||
let cols = columns.iter().map(|c| file.display(c, cache)).collect();
|
||||
dest.push((depth, cols));
|
||||
|
||||
if recurse {
|
||||
if let Some(ref dir) = file.this {
|
||||
get_files(columns, cache, recurse, dest, dir.files(true).as_slice(), depth + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user