From e63c65ab7d944fb3a80f6c40bd0d98ec50b054c6 Mon Sep 17 00:00:00 2001 From: rabite Date: Mon, 11 Mar 2019 12:08:43 +0100 Subject: [PATCH] tagging files --- src/fail.rs | 5 ++++ src/files.rs | 74 +++++++++++++++++++++++++++++++++++++++++++++++-- src/listview.rs | 19 +++++++++++-- src/main.rs | 4 +++ 4 files changed, 98 insertions(+), 4 deletions(-) diff --git a/src/fail.rs b/src/fail.rs index 90def78..16a6028 100644 --- a/src/fail.rs +++ b/src/fail.rs @@ -54,6 +54,8 @@ pub enum HError { StripPrefixError{#[cause] error: std::path::StripPrefixError}, #[fail(display = "INofify failed: {}", error)] INotifyError{#[cause] error: notify::Error}, + #[fail(display = "Tags not loaded yet")] + TagsNotLoadedYetError } impl HError { @@ -73,6 +75,9 @@ impl HError { pub fn popup_finnished() -> HResult { Err(HError::PopupFinnished) } + pub fn tags_not_loaded() -> HResult { + Err(HError::TagsNotLoadedYetError) + } } pub trait ErrorLog where Self: Sized { diff --git a/src/files.rs b/src/files.rs index a5e2daa..b151e39 100644 --- a/src/files.rs +++ b/src/files.rs @@ -19,6 +19,32 @@ use crate::fail::{HResult, HError}; lazy_static! { static ref COLORS: LsColors = LsColors::from_env().unwrap(); + static ref TAGS: Mutex<(bool, Vec)> = Mutex::new((false, vec![])); +} + +pub fn load_tags() -> HResult<()> { + std::thread::spawn(|| -> HResult<()> { + let tag_path = crate::paths::tagfile_path()?; + let tags = std::fs::read_to_string(tag_path)?; + let mut tags = tags.lines().map(|f| PathBuf::from(f)).collect::>(); + let mut tag_lock = TAGS.lock()?; + tag_lock.0 = true; + tag_lock.1.append(&mut tags); + Ok(()) + }); + Ok(()) +} + +pub fn check_tag(path: &PathBuf) -> HResult { + tags_loaded()?; + let tagged = TAGS.lock()?.1.contains(path); + Ok(tagged) +} + +pub fn tags_loaded() -> HResult<()> { + let loaded = TAGS.lock()?.0; + if loaded { Ok(()) } + else { HError::tags_not_loaded() } } #[derive(PartialEq, Eq, Hash, Clone, Debug)] @@ -312,7 +338,8 @@ pub struct File { pub kind: Kind, pub color: Option, pub meta: Option, - pub selected: bool + pub selected: bool, + pub tag: Option // flags: Option, } @@ -321,13 +348,16 @@ impl File { name: &str, path: PathBuf, ) -> File { + let tag = check_tag(&path).ok(); + File { name: name.to_string(), kind: if path.is_dir() { Kind::Directory } else { Kind::File }, path: path, meta: None, color: None, - selected: false + selected: false, + tag: tag, } } @@ -461,6 +491,46 @@ impl File { self.selected } + pub fn is_tagged(&self) -> HResult { + if let Some(tag) = self.tag { + return Ok(tag); + } + let tag = check_tag(&self.path)?; + Ok(tag) + } + + pub fn toggle_tag(&mut self) -> HResult<()> { + let new_state = match self.tag { + Some(tag) => !tag, + None => { + let tag = check_tag(&self.path); + !tag? + } + }; + self.tag = Some(new_state); + + match new_state { + true => TAGS.lock()?.1.push(self.path.clone()), + false => { TAGS.lock()?.1.remove_item(&self.path); }, + } + self.save_tags()?; + Ok(()) + } + + pub fn save_tags(&self) -> HResult<()> { + std::thread::spawn(|| -> HResult<()> { + let tagfile_path = crate::paths::tagfile_path()?; + let tags = TAGS.lock()?.clone(); + let tags_str = tags.1.iter().map(|p| { + let path = p.to_string_lossy().to_string(); + format!("{}\n", path) + }).collect::(); + std::fs::write(tagfile_path, tags_str)?; + Ok(()) + }); + Ok(()) + } + pub fn pretty_print_permissions(&self) -> HResult { let perms: usize = format!("{:o}", self.meta()?.mode()).parse().unwrap(); let perms: usize = perms % 800; diff --git a/src/listview.rs b/src/listview.rs index 64ec5ac..b0db7ae 100644 --- a/src/listview.rs +++ b/src/listview.rs @@ -46,6 +46,7 @@ impl Listable for ListView { Key::Left => self.goto_grand_parent()?, Key::Right => self.goto_selected()?, Key::Char(' ') => self.multi_select_file(), + Key::Char('t') => self.toggle_tag()?, Key::Char('h') => self.toggle_hidden(), Key::Char('r') => self.reverse_sort(), Key::Char('s') => self.cycle_sort(), @@ -136,6 +137,11 @@ where fn render_line(&self, file: &File) -> String { let name = &file.name; let (size, unit) = file.calculate_size().unwrap_or((0, "".to_string())); + let tag = match file.is_tagged() { + Ok(true) => term::color_red() + "*", + _ => "".to_string() + }; + let tag_len = if tag != "" { 1 } else { 0 }; let selection_gap = " ".to_string(); let (name, selection_color) = if file.is_selected() { @@ -149,18 +155,21 @@ where + unit.to_string().len() as u16); let padding = sized_string.len() - sized_string.width_cjk(); let padding = xsize - padding as u16; + let padding = padding - tag_len; format!( "{}{}{}{}{}{}{}", termion::cursor::Save, match &file.color { - Some(color) => format!("{}{}{:padding$}{}", + Some(color) => format!("{}{}{}{:padding$}{}", + tag, term::from_lscolor(color), selection_color, &sized_string, term::normal_color(), padding = padding as usize), - _ => format!("{}{}{:padding$}{}", + _ => format!("{}{}{}{:padding$}{}", + tag, term::normal_color(), selection_color, &sized_string, @@ -335,6 +344,12 @@ impl ListView self.refresh().log(); } + fn toggle_tag(&mut self) -> HResult<()> { + self.selected_file_mut().toggle_tag()?; + self.move_down(); + Ok(()) + } + fn find_file(&mut self) -> HResult<()> { let name = self.minibuffer("find")?; let file = self.content.files.iter().find(|file| { diff --git a/src/main.rs b/src/main.rs index f90f8ef..45679eb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -66,6 +66,10 @@ fn main() -> HResult<()> { } fn run() -> HResult<()> { + // do this early so it might be ready when needed + crate::files::load_tags()?; + + let bufout = std::io::BufWriter::new(std::io::stdout()); // Need to do this here to actually turn terminal into raw mode... let mut screen = AlternateScreen::from(bufout);