use std::borrow::Cow;
use std::io::{self, Write};
use std::sync::Mutex;
use users::{Groups, Users, UsersCache};
use crate::file_sizes::Size;
use crate::flat_tree::{LineType, Tree, TreeLine};
use crate::patterns::Pattern;
use crate::screens::{Screen, ScreenArea};
use crate::skin::Skin;
pub struct TreeView<'a> {
pub w: u16,
pub h: u16, pub out: &'a mut Write,
pub skin: &'a Skin,
pub in_app: bool,
}
impl TreeView<'_> {
pub fn from_screen(screen: &mut Screen) -> TreeView<'_> {
TreeView {
w: screen.w,
h: screen.h-2,
out: &mut screen.stdout,
skin: &screen.skin,
in_app: true,
}
}
pub fn write_tree(&mut self, tree: &Tree) -> io::Result<()> {
lazy_static! {
static ref USERS_CACHE_MUTEX: Mutex<UsersCache> = Mutex::new(UsersCache::new());
}
let users_cache = USERS_CACHE_MUTEX.lock().unwrap();
let mut max_user_name_len = 0;
let mut max_group_name_len = 0;
if tree.options.show_permissions {
for i in 1..tree.lines.len() {
let line = &tree.lines[i];
if let Some(user) = users_cache.get_user_by_uid(line.uid) {
max_user_name_len = max_user_name_len.max(user.name().to_string_lossy().len());
}
if let Some(group) = users_cache.get_group_by_gid(line.gid) {
max_group_name_len =
max_group_name_len.max(group.name().to_string_lossy().len());
}
}
}
let total_size = tree.total_size();
let area = ScreenArea {
top: 1,
bottom: self.h + 1,
scroll: tree.scroll,
content_length: tree.lines.len() as i32,
width: self.w,
};
let scrollbar = area.scrollbar();
if self.in_app {
write!(self.out, "{}", termion::cursor::Goto(1, 1))?;
}
for y in 1..self.h + 1 {
let mut line_index = (y - 1) as usize;
if line_index > 0 {
line_index += tree.scroll as usize;
}
if line_index < tree.lines.len() {
let line = &tree.lines[line_index];
write!(self.out, "{}", self.skin.tree.fgbg())?;
for depth in 0..line.depth {
write!(
self.out,
"{}",
if line.left_branchs[depth as usize] {
if tree.has_branch(line_index + 1, depth as usize) {
if depth == line.depth - 1 {
"├──"
} else {
"│ "
}
} else {
"└──"
}
} else {
" "
},
)?;
}
if tree.options.show_sizes && line_index > 0 {
self.write_line_size(line, total_size)?;
}
if tree.options.show_permissions && line_index > 0 {
if line.is_selectable() {
self.write_mode(line.mode)?;
if let Some(user) = users_cache.get_user_by_uid(line.uid) {
write!(
self.out,
" {:w$}",
user.name().to_string_lossy(),
w = max_user_name_len,
)?;
}
if let Some(group) = users_cache.get_group_by_gid(line.gid) {
write!(
self.out,
" {:w$} ",
group.name().to_string_lossy(),
w = max_group_name_len,
)?;
}
} else {
write!(
self.out,
"{}──────────────{}",
self.skin.tree.fg, self.skin.reset.fg,
)?;
}
}
if self.in_app && line_index == tree.selection {
write!(self.out, "{}", self.skin.selected_line.bg)?;
}
self.write_line_name(line, line_index, &tree.options.pattern)?;
} else if !self.in_app {
write!(self.out, "\r\n",)?;
break; }
write!(self.out, "{}", self.skin.style_reset)?;
if self.in_app {
write!(self.out, "{}", termion::clear::UntilNewline)?;
if let Some((sctop, scbottom)) = scrollbar {
if sctop <= y && y <= scbottom {
write!(self.out, "{}▐", termion::cursor::Goto(self.w, y),)?;
}
}
}
write!(self.out, "\r\n",)?;
}
self.out.flush()?;
Ok(())
}
fn write_mode(&mut self, mode: u32) -> io::Result<()> {
write!(
self.out,
"{}{}{}{}{}{}{}{}{}{}",
self.skin.permissions.fg,
if (mode & (1 << 8)) != 0 { 'r' } else { '-' },
if (mode & (1 << 7)) != 0 { 'w' } else { '-' },
if (mode & (1 << 6)) != 0 { 'x' } else { '-' },
if (mode & (1 << 5)) != 0 { 'r' } else { '-' },
if (mode & (1 << 4)) != 0 { 'w' } else { '-' },
if (mode & (1 << 3)) != 0 { 'x' } else { '-' },
if (mode & (1 << 2)) != 0 { 'r' } else { '-' },
if (mode & (1 << 1)) != 0 { 'w' } else { '-' },
if (mode & 1) != 0 { 'x' } else { '-' },
)
}
fn write_line_size(&mut self, line: &TreeLine, total_size: Size) -> io::Result<()> {
if let Some(s) = line.size {
let dr: usize = s.discrete_ratio(total_size, 8) as usize;
let s: Vec<char> = s.to_string().chars().collect();
write!(
self.out,
"{}{}",
self.skin.size_text.fg, self.skin.size_bar_full.bg,
)?;
for i in 0..dr {
write!(self.out, "{}", if i < s.len() { s[i] } else { ' ' })?;
}
write!(self.out, "{}", self.skin.size_bar_void.bg)?;
for i in dr..8 {
write!(self.out, "{}", if i < s.len() { s[i] } else { ' ' })?;
}
write!(self.out, "{}{} ", self.skin.reset.fg, self.skin.reset.bg,)
} else {
write!(
self.out,
"{}────────{} ",
self.skin.tree.fg, self.skin.reset.fg,
)
}
}
fn write_line_name(
&mut self,
line: &TreeLine,
idx: usize,
pattern: &Pattern,
) -> io::Result<()> {
match &line.line_type {
LineType::Dir => {
if idx == 0 {
write!(
self.out,
"{}{}{}",
self.skin.style_folder,
&self.skin.directory.fg,
&line.path.to_string_lossy(),
)?;
} else {
write!(
self.out,
"{}{}{}",
self.skin.style_folder,
&self.skin.directory.fg,
decorated_name(
&line.name,
pattern,
&self.skin.char_match.fg,
&self.skin.directory.fg
),
)?;
if line.unlisted > 0 {
write!(self.out, " …",)?;
}
}
}
LineType::File => {
if line.is_exe() {
write!(
self.out,
"{}{}",
&self.skin.exe.fg,
decorated_name(
&line.name,
pattern,
&self.skin.char_match.fg,
&self.skin.exe.fg
),
)?;
} else {
write!(
self.out,
"{}{}",
&self.skin.file.fg,
decorated_name(
&line.name,
pattern,
&self.skin.char_match.fg,
&self.skin.file.fg
),
)?;
}
}
LineType::SymLinkToFile(target) => {
write!(
self.out,
"{}{} {}->{} {}",
&self.skin.link.fg,
decorated_name(
&line.name,
pattern,
&self.skin.char_match.fg,
&self.skin.link.fg
),
if line.has_error {
&self.skin.file_error.fg
} else {
&self.skin.link.fg
},
&self.skin.file.fg,
&target,
)?;
}
LineType::SymLinkToDir(target) => {
write!(
self.out,
"{}{} {}->{}{} {}",
&self.skin.link.fg,
decorated_name(
&line.name,
pattern,
&self.skin.char_match.fg,
&self.skin.link.fg
),
if line.has_error {
&self.skin.file_error.fg
} else {
&self.skin.link.fg
},
self.skin.style_folder,
&self.skin.directory.fg,
&target,
)?;
}
LineType::Pruning => {
write!(
self.out,
"{}{}{} unlisted",
self.skin.unlisted.fg,
self.skin.style_pruning,
&line.unlisted,
)?;
}
}
Ok(())
}
}
fn decorated_name<'a>(
name: &'a str,
pattern: &Pattern,
prefix: &str,
postfix: &str,
) -> Cow<'a, str> {
if pattern.is_some() {
if let Some(m) = pattern.find(name) {
return Cow::Owned(m.wrap_matching_chars(name, prefix, postfix));
}
}
Cow::Borrowed(name)
}