use std::cmp::{self, Ordering};
use std::fs;
use std::mem;
use std::path::{Path, PathBuf};
use crate::errors;
use crate::file_sizes::Size;
use crate::task_sync::TaskLifetime;
use crate::tree_build::TreeBuilder;
use crate::tree_options::TreeOptions;
#[derive(Debug, Clone, PartialEq)]
pub enum LineType {
File,
Dir,
SymLinkToDir(String), SymLinkToFile(String), Pruning, }
#[derive(Debug)]
pub struct TreeLine {
pub left_branchs: Box<[bool]>, pub depth: u16,
pub name: String, pub path: PathBuf,
pub line_type: LineType,
pub has_error: bool,
pub nb_kept_children: usize,
pub unlisted: usize, pub score: i32, pub size: Option<Size>, pub mode: u32, pub uid: u32, pub gid: u32, }
#[derive(Debug)]
pub struct Tree {
pub lines: Box<[TreeLine]>,
pub selection: usize, pub options: TreeOptions,
pub scroll: i32, pub nb_gitignored: u32, }
impl TreeLine {
pub fn is_selectable(&self) -> bool {
match &self.line_type {
LineType::Pruning => false,
_ => true,
}
}
pub fn is_dir(&self) -> bool {
match &self.line_type {
LineType::Dir => true,
LineType::SymLinkToDir(_) => true,
_ => false,
}
}
pub fn is_file(&self) -> bool {
match &self.line_type {
LineType::File => true,
_ => false,
}
}
pub fn is_exe(&self) -> bool {
(self.mode & 0o111) != 0
}
pub fn target(&self) -> PathBuf {
match &self.line_type {
LineType::SymLinkToFile(target) | LineType::SymLinkToDir(target) => {
let mut target_path = PathBuf::from(target);
if target_path.is_relative() {
target_path = self.path.parent().unwrap().join(target_path);
}
if let Ok(canonic) = fs::canonicalize(&target_path) {
target_path = canonic;
}
target_path
}
_ => self.path.clone(),
}
}
}
impl PartialEq for TreeLine {
fn eq(&self, other: &TreeLine) -> bool {
self.path == other.path
}
}
impl Eq for TreeLine {}
impl Ord for TreeLine {
fn cmp(&self, other: &TreeLine) -> Ordering {
let mut sci = self.path.components();
let mut oci = other.path.components();
loop {
match sci.next() {
Some(sc) => {
match oci.next() {
Some(oc) => {
let scs = sc.as_os_str().to_string_lossy();
let ocs = oc.as_os_str().to_string_lossy();
let lower_ordering = scs.to_lowercase().cmp(&ocs.to_lowercase());
if lower_ordering != Ordering::Equal {
return lower_ordering;
}
let ordering = scs.cmp(&ocs);
if ordering != Ordering::Equal {
return ordering;
}
}
None => {
return Ordering::Greater;
}
};
}
None => {
if oci.next().is_some() {
return Ordering::Less;
} else {
return Ordering::Equal;
}
}
};
}
}
}
impl PartialOrd for TreeLine {
fn partial_cmp(&self, other: &TreeLine) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Tree {
pub fn refresh(
&mut self,
page_height: usize,
) -> Result<(), errors::TreeBuildError> {
let builder = TreeBuilder::from(
self.root().to_path_buf(),
self.options.clone(),
page_height,
)?;
let mut tree = builder.build(&TaskLifetime::unlimited()).unwrap(); let selected_path = self.selected_line().path.to_path_buf();
mem::swap(&mut self.lines, &mut tree.lines);
self.try_select_path(&selected_path);
self.make_selection_visible(page_height as i32);
Ok(())
}
pub fn after_lines_changed(&mut self) {
self.lines.sort();
for i in 1..self.lines.len() {
for d in 0..self.lines[i].left_branchs.len() {
self.lines[i].left_branchs[d] = false;
}
}
let mut last_parent_index: usize = self.lines.len() + 1;
for end_index in (1..self.lines.len()).rev() {
let depth = (self.lines[end_index].depth - 1) as usize;
let start_index = {
let parent_index = {
let parent_path = &self.lines[end_index].path.parent();
match parent_path {
Some(parent_path) => {
let mut index = end_index;
loop {
index -= 1;
if self.lines[index].path == *parent_path {
break;
}
if index == 0 {
break;
}
}
index
}
None => end_index, }
};
if parent_index != last_parent_index {
let unlisted = self.lines[parent_index].unlisted;
if unlisted > 0 && self.lines[end_index].nb_kept_children == 0 {
self.lines[end_index].line_type = LineType::Pruning;
self.lines[end_index].unlisted = unlisted + 1;
self.lines[parent_index].unlisted = 0;
}
last_parent_index = parent_index;
}
parent_index + 1
};
for i in start_index..=end_index {
self.lines[i].left_branchs[depth] = true;
}
}
}
pub fn has_branch(&self, line_index: usize, depth: usize) -> bool {
if line_index >= self.lines.len() {
return false;
}
let line = &self.lines[line_index];
depth < usize::from(line.depth) && line.left_branchs[depth]
}
pub fn move_selection(&mut self, dy: i32, page_height: i32) {
let l = self.lines.len();
loop {
self.selection = (self.selection + ((l as i32) + dy) as usize) % l;
if self.lines[self.selection].is_selectable() {
break;
}
}
let l = l as i32;
let sel = self.selection as i32;
if dy < 0 && sel < self.scroll + 5 {
self.scroll = (self.scroll + 2 * dy).max(0);
} else if dy > 0 && l > page_height && sel > self.scroll + page_height - 5 {
self.scroll += 2 * dy;
}
}
pub fn try_scroll(&mut self, dy: i32, page_height: i32) {
self.scroll = (self.scroll + dy).max(0).min(self.lines.len() as i32 - 5);
self.select_visible_line(page_height);
}
pub fn select_visible_line(&mut self, page_height: i32) {
let sel = self.selection as i32;
if sel < self.scroll || sel >= self.scroll + page_height {
self.selection = self.scroll as usize;
let l = self.lines.len();
loop {
self.selection = (self.selection + ((l as i32) + 1) as usize) % l;
if self.lines[self.selection].is_selectable() {
break;
}
}
}
}
pub fn make_selection_visible(&mut self, page_height: i32) {
let sel = self.selection as i32;
let l = self.lines.len() as i32;
if sel < self.scroll {
self.scroll = (self.selection as i32 - 2).max(0);
} else if l > page_height && sel >= self.scroll + page_height {
self.scroll = (self.selection as i32 - page_height + 2) as i32;
}
}
pub fn selected_line(&self) -> &TreeLine {
&self.lines[self.selection]
}
pub fn root(&self) -> &PathBuf {
&self.lines[0].path
}
pub fn try_select_best_match(&mut self) {
let mut best_score = 0;
for (idx, line) in self.lines.iter().enumerate() {
if !line.is_selectable() {
continue;
}
if best_score > line.score {
continue;
}
if line.score == best_score {
if self.lines[idx].depth >= self.lines[self.selection].depth {
continue;
}
}
best_score = line.score;
self.selection = idx;
}
}
pub fn try_select_path(&mut self, path: &Path) {
for (idx, line) in self.lines.iter().enumerate() {
if !line.is_selectable() {
continue;
}
if path == line.path {
self.selection = idx;
return;
}
}
}
pub fn try_select_next_match(&mut self) -> bool {
for di in 0..self.lines.len() {
let idx = (self.selection + di + 1) % self.lines.len();
let line = &self.lines[idx];
if !line.is_selectable() {
continue;
}
if line.score > 0 {
self.selection = idx;
return true;
}
}
false
}
pub fn has_dir_missing_size(&self) -> bool {
if !self.options.show_sizes {
return false;
}
for i in 1..self.lines.len() {
if self.lines[i].size.is_none() && self.lines[i].line_type == LineType::Dir {
return true;
}
}
false
}
pub fn fetch_file_sizes(&mut self) {
for i in 1..self.lines.len() {
if self.lines[i].is_file() {
self.lines[i].size = Some(Size::from_file(&self.lines[i].path));
}
}
}
pub fn fetch_some_missing_dir_size(&mut self, tl: &TaskLifetime) {
for i in 1..self.lines.len() {
if self.lines[i].size.is_none() && self.lines[i].line_type == LineType::Dir {
self.lines[i].size = Size::from_dir(&self.lines[i].path, tl);
return;
}
}
}
pub fn total_size(&self) -> Size {
if let Some(size) = self.lines[0].size {
size
} else {
let mut sum = Size::from(0);
for i in 1..self.lines.len() {
if self.lines[i].depth == 1 {
if let Some(size) = self.lines[i].size {
sum += size;
}
}
}
sum
}
}
}