use termion::event::{Event, Key}; use unicode_width::UnicodeWidthStr; use std::path::{Path, PathBuf}; use crate::coordinates::{Coordinates, Position, Size}; use crate::files::{File, Files}; use crate::fail::HResult; use crate::term; use crate::widget::{Widget}; pub trait Listable { fn len(&self) -> usize; fn render(&self) -> Vec; fn on_refresh(&mut self) {} fn on_key(&mut self, _key: Key) {} } impl Listable for ListView { fn len(&self) -> usize { self.content.len() } fn render(&self)-> Vec { self.render() } fn on_refresh(&mut self) { let visible_file_num = self.selection + self.get_coordinates().ysize() as usize; self.content.meta_upto(visible_file_num); } fn on_key(&mut self, key: Key) { match key { Key::Up | Key::Char('p') => { self.move_up(); self.refresh(); } Key::Char('P') => { for _ in 0..10 { self.move_up() } self.refresh(); } Key::Char('N') => { for _ in 0..10 { self.move_down() } self.refresh(); } Key::Down | Key::Char('n') => { self.move_down(); self.refresh(); }, Key::Ctrl('s') => { self.find_file().ok(); } Key::Left => self.goto_grand_parent(), Key::Right => self.goto_selected(), Key::Char(' ') => self.multi_select_file(), Key::Char('h') => self.toggle_hidden(), Key::Char('r') => self.reverse_sort(), Key::Char('s') => self.cycle_sort(), Key::Char('K') => self.select_next_mtime(), Key::Char('k') => self.select_prev_mtime(), Key::Char('d') => self.toggle_dirs_first(), _ => self.bad(Event::Key(key)) } } } #[derive(PartialEq)] pub struct ListView where ListView: Listable { pub content: T, lines: usize, selection: usize, offset: usize, buffer: Vec, coordinates: Coordinates, seeking: bool, } impl ListView where ListView: Widget, ListView: Listable { pub fn new(content: T) -> ListView { let view = ListView:: { content: content, lines: 0, selection: 0, offset: 0, buffer: Vec::new(), coordinates: Coordinates { size: Size((1, 1)), position: Position((1, 1)), }, seeking: false }; view } pub fn move_up(&mut self) { if self.selection == 0 { return; } if self.selection - self.offset <= 0 { self.offset -= 1; } self.selection -= 1; self.seeking = false; } pub fn move_down(&mut self) { let lines = self.lines; let y_size = self.coordinates.ysize() as usize; if self.lines == 0 || self.selection == lines - 1 { return; } if self.selection + 1 >= y_size && self.selection + 1 - self.offset >= y_size { self.offset += 1; } self.selection += 1; self.seeking = false; } pub fn get_selection(&self) -> usize { self.selection } fn set_selection(&mut self, position: usize) { let ysize = self.coordinates.ysize() as usize; let mut offset = 0; while position + 2 >= ysize + offset { offset += 1 } self.offset = offset; self.selection = position; } fn render_line(&self, file: &File) -> String { let name = &file.name; let (size, unit) = file.calculate_size().unwrap_or((0, "".to_string())); let selection_gap = " ".to_string(); let (name, selection_color) = if file.is_selected() { (selection_gap + name, crate::term::color_yellow()) } else { (name.clone(), "".to_string()) }; let xsize = self.get_coordinates().xsize(); let sized_string = term::sized_string(&name, xsize); let size_pos = xsize - (size.to_string().len() as u16 + unit.to_string().len() as u16); let padding = sized_string.len() - sized_string.width_cjk(); let padding = xsize - padding as u16; format!( "{}{}{}{}{}{}{}", termion::cursor::Save, match &file.color { Some(color) => format!("{}{}{:padding$}{}", term::from_lscolor(color), selection_color, &sized_string, term::normal_color(), padding = padding as usize), _ => format!("{}{}{:padding$}{}", term::normal_color(), selection_color, &sized_string, term::normal_color(), padding = padding as usize), } , termion::cursor::Restore, termion::cursor::Right(size_pos), term::highlight_color(), size, unit ) } } impl ListView { pub fn selected_file(&self) -> &File { let selection = self.selection; let file = &self.content[selection]; file } pub fn selected_file_mut(&mut self) -> &mut File { let selection = self.selection; let file = &mut self.content.files[selection]; file } pub fn clone_selected_file(&self) -> File { let selection = self.selection; let file = self.content[selection].clone(); file } pub fn grand_parent(&self) -> Option { self.selected_file().grand_parent() } pub fn goto_grand_parent(&mut self) { match self.grand_parent() { Some(grand_parent) => self.goto_path(&grand_parent), None => self.show_status("Can't go further!"), } } fn goto_selected(&mut self) { let path = self.selected_file().path(); self.goto_path(&path); } pub fn goto_path(&mut self, path: &Path) { match crate::files::Files::new_from_path(path) { Ok(files) => { self.content = files; self.selection = 0; self.offset = 0; self.refresh(); } Err(err) => { self.show_status(&format!("Can't open this path: {}", err)); return; } } } pub fn select_file(&mut self, file: &File) { let pos = self .content .files .iter() .position(|item| item == file) .unwrap_or(0); self.set_selection(pos); } fn cycle_sort(&mut self) { let file = self.clone_selected_file(); self.content.cycle_sort(); self.content.sort(); self.select_file(&file); self.refresh(); self.show_status(&format!("Sorting by: {}", self.content.sort)); } fn reverse_sort(&mut self) { let file = self.clone_selected_file(); self.content.reverse_sort(); self.content.sort(); self.select_file(&file); self.refresh(); self.show_status(&format!("Reversed sorting by: {}", self.content.sort)); } fn select_next_mtime(&mut self) { let file = self.clone_selected_file(); let dir_settings = self.content.dirs_first; let sort_settings = self.content.sort; self.content.dirs_first = false; self.content.sort = crate::files::SortBy::MTime; self.content.sort(); self.select_file(&file); if self.seeking == false || self.selection + 1 == self.content.len() { self.selection = 0; self.offset = 0; } else { self.move_down(); } let file = self.clone_selected_file(); self.content.dirs_first = dir_settings; self.content.sort = sort_settings; self.content.sort(); self.select_file(&file); self.seeking = true; self.refresh(); } fn select_prev_mtime(&mut self) { let file = self.clone_selected_file(); let dir_settings = self.content.dirs_first; let sort_settings = self.content.sort; self.content.dirs_first = false; self.content.sort = crate::files::SortBy::MTime; self.content.sort(); self.select_file(&file); if self.seeking == false || self.selection == 0 { self.set_selection(self.content.len() - 1); } else { self.move_up(); } let file = self.clone_selected_file(); self.content.dirs_first = dir_settings; self.content.sort = sort_settings; self.content.sort(); self.select_file(&file); self.seeking = true; self.refresh(); } fn toggle_hidden(&mut self) { let file = self.clone_selected_file(); self.content.toggle_hidden(); self.content.reload_files(); self.select_file(&file); self.refresh(); } fn toggle_dirs_first(&mut self) { let file = self.clone_selected_file(); self.content.dirs_first = !self.content.dirs_first; self.content.sort(); self.select_file(&file); self.refresh(); self.show_status(&format!("Direcories first: {}", self.content.dirs_first)); } fn multi_select_file(&mut self) { let file = self.selected_file_mut(); file.toggle_selection(); self.move_down(); self.refresh(); } fn find_file(&mut self) -> HResult<()> { let name = self.minibuffer("find")?; let file = self.content.files.iter().find(|file| { if file.name.to_lowercase().contains(&name) { true } else { false } })?.clone(); self.select_file(&file); Ok(()) } fn render(&self) -> Vec { let ysize = self.get_coordinates().ysize() as usize; let offset = self.offset; self.content .files .iter() .skip(offset) .take(ysize) .map(|file| self.render_line(&file)) .collect() } } impl Widget for ListView where ListView: Listable { fn get_coordinates(&self) -> &Coordinates { &self.coordinates } fn set_coordinates(&mut self, coordinates: &Coordinates) { if self.coordinates == *coordinates { return; } self.coordinates = coordinates.clone(); self.refresh(); } fn refresh(&mut self) { self.on_refresh(); self.lines = self.len(); if self.selection >= self.lines && self.selection != 0 { self.selection -= 1; } self.buffer = self.render(); } fn get_drawlist(&self) -> String { let mut output = term::reset(); let (xpos, ypos) = self.coordinates.position().position(); output += &self .buffer .iter() .enumerate() .map(|(i, item)| { let mut output = term::normal_color(); if i == (self.selection - self.offset) { output += &term::invert(); } output += &format!( "{}{}{}", term::goto_xy(xpos, i as u16 + ypos), item, term::reset() ); String::from(output) }) .collect::(); output += &self.get_redraw_empty_list(self.buffer.len()); output } fn render_header(&self) -> String { format!("{} files", self.len()) } fn on_key(&mut self, key: Key) -> HResult<()> { Listable::on_key(self, key); Ok(()) } }