diff options
Diffstat (limited to 'src/ui/ncurses_backend.rs')
-rw-r--r-- | src/ui/ncurses_backend.rs | 464 |
1 files changed, 464 insertions, 0 deletions
diff --git a/src/ui/ncurses_backend.rs b/src/ui/ncurses_backend.rs new file mode 100644 index 0000000..a55aa9f --- /dev/null +++ b/src/ui/ncurses_backend.rs @@ -0,0 +1,464 @@ +use crate::config::{JoshutoColorTheme, JoshutoConfig}; +use crate::context::JoshutoContext; +use crate::fs::{JoshutoDirEntry, JoshutoDirList}; +use crate::unix; +use crate::window; + +use crate::THEME_T; + +use std::fs; +use std::sync::Mutex; +use std::time; + +use lazy_static::lazy_static; +use users::mock::{Groups, Users}; +use users::UsersCache; + +pub const ERR_COLOR: i16 = 240; +pub const EMPTY_COLOR: i16 = 241; + +const MIN_WIN_WIDTH: usize = 4; + +pub struct DisplayOptions { + pub detailed: bool, +} + +pub const PRIMARY_DISPLAY_OPTION: DisplayOptions = DisplayOptions { detailed: true }; +pub const SECONDARY_DISPLAY_OPTION: DisplayOptions = DisplayOptions { detailed: false }; + +pub fn init_ncurses() { + ncurses::setlocale(ncurses::LcCategory::all, ""); + + ncurses::initscr(); + ncurses::cbreak(); + + ncurses::start_color(); + ncurses::use_default_colors(); + ncurses::noecho(); + ncurses::set_escdelay(0); + ncurses::curs_set(ncurses::CURSOR_VISIBILITY::CURSOR_INVISIBLE); + + process_theme(); + + ncurses::addstr("Loading..."); + ncurses::refresh(); +} + +fn process_theme() { + for pair in THEME_T.colorpair.iter() { + ncurses::init_pair(pair.id, pair.fg, pair.bg); + } + + /* error message */ + ncurses::init_pair(ERR_COLOR, ncurses::COLOR_RED, -1); + /* empty */ + ncurses::init_pair(EMPTY_COLOR, ncurses::COLOR_WHITE, ncurses::COLOR_RED); +} + +pub fn end_ncurses() { + ncurses::endwin(); +} + +pub fn getmaxyx() -> (i32, i32) { + let mut term_rows: i32 = 0; + let mut term_cols: i32 = 0; + ncurses::getmaxyx(ncurses::stdscr(), &mut term_rows, &mut term_cols); + (term_rows, term_cols) +} + +pub fn display_menu<I, S>(win: &window::JoshutoPanel, items: I) +where + I: IntoIterator<Item = S>, + S: AsRef<str>, +{ + ncurses::werase(win.win); + ncurses::mvwhline(win.win, 0, 0, 0, win.cols); + + for (i, val) in items.into_iter().enumerate() { + ncurses::wmove(win.win, (i + 1) as i32, 0); + ncurses::waddstr(win.win, val.as_ref()); + } + ncurses::wnoutrefresh(win.win); +} + +pub fn wprint_msg(win: &window::JoshutoPanel, msg: &str) { + ncurses::werase(win.win); + ncurses::mvwaddstr(win.win, 0, 0, msg); + ncurses::wnoutrefresh(win.win); +} + +pub fn wprint_err(win: &window::JoshutoPanel, msg: &str) { + let attr = ncurses::A_BOLD() | ncurses::COLOR_PAIR(ERR_COLOR); + + ncurses::werase(win.win); + ncurses::wattron(win.win, attr); + + ncurses::mvwaddstr(win.win, 0, 0, msg); + + ncurses::wattroff(win.win, attr); + ncurses::wnoutrefresh(win.win); +} + +pub fn wprint_empty(win: &window::JoshutoPanel, msg: &str) { + ncurses::werase(win.win); + ncurses::wattron(win.win, ncurses::COLOR_PAIR(EMPTY_COLOR)); + ncurses::mvwaddstr(win.win, 0, 0, msg); + ncurses::wattroff(win.win, ncurses::COLOR_PAIR(EMPTY_COLOR)); + ncurses::wnoutrefresh(win.win); +} + +fn wprint_file_name( + win: &window::JoshutoPanel, + file_name: &str, + coord: (i32, i32), + mut space_avail: usize, +) { + let name_visual_space = unicode_width::UnicodeWidthStr::width(file_name); + if name_visual_space < space_avail { + ncurses::mvwaddstr(win.win, coord.0, coord.1, &file_name); + return; + } + if let Some(ext) = file_name.rfind('.') { + let extension: &str = &file_name[ext..]; + let ext_len = unicode_width::UnicodeWidthStr::width(extension); + if space_avail > ext_len { + space_avail -= ext_len; + ncurses::mvwaddstr(win.win, coord.0, space_avail as i32, &extension); + } + } + if space_avail < 2 { + return; + } else { + space_avail -= 2; + } + + ncurses::wmove(win.win, coord.0, coord.1); + + let mut trim_index: usize = file_name.len(); + + let mut total: usize = 0; + for (index, ch) in file_name.char_indices() { + if total >= space_avail { + trim_index = index; + break; + } + total += unicode_width::UnicodeWidthChar::width(ch).unwrap_or(2); + } + ncurses::waddstr(win.win, &file_name[..trim_index]); + ncurses::waddstr(win.win, "…"); +} + +fn wprint_entry( + win: &window::JoshutoPanel, + file: &JoshutoDirEntry, + prefix: (usize, &str), + coord: (i32, i32), +) { + if win.cols <= prefix.0 as i32 { + return; + } + ncurses::waddstr(win.win, prefix.1); + let space_avail = win.cols as usize - prefix.0; + + wprint_file_name( + &win, + file.file_name(), + (coord.0, coord.1 + prefix.0 as i32), + space_avail, + ); +} + +fn wprint_entry_detailed( + win: &window::JoshutoPanel, + file: &JoshutoDirEntry, + prefix: (usize, &str), + coord: (i32, i32), +) { + if win.cols <= prefix.0 as i32 { + return; + } + ncurses::waddstr(win.win, prefix.1); + let mut space_avail = win.cols as usize - prefix.0; + + let coord = (coord.0, coord.1 + prefix.0 as i32); + + if file.file_path().is_dir() { + } else { + let file_size_string = file_size_to_string(file.metadata.len as f64); + if space_avail > file_size_string.len() { + space_avail -= file_size_string.len(); + ncurses::mvwaddstr(win.win, coord.0, space_avail as i32, &file_size_string); + } + } + wprint_file_name(win, file.file_name(), coord, space_avail); +} + +pub fn display_contents( + win: &window::JoshutoPanel, + dirlist: &mut JoshutoDirList, + config_t: &JoshutoConfig, + options: &DisplayOptions, +) { + if win.cols < MIN_WIN_WIDTH as i32 { + return; + } + let dir_len = dirlist.contents.len(); + if dir_len == 0 { + wprint_empty(win, "empty"); + return; + } + ncurses::werase(win.win); + ncurses::wmove(win.win, 0, 0); + + let draw_func = if options.detailed { + wprint_entry_detailed + } else { + wprint_entry + }; + + let curr_index = dirlist.index.unwrap(); + dirlist + .pagestate + .update_page_state(curr_index, win.rows, dir_len, config_t.scroll_offset); + + let (start, end) = (dirlist.pagestate.start, dirlist.pagestate.end); + let dir_contents = &dirlist.contents[start..end]; + + ncurses::werase(win.win); + ncurses::wmove(win.win, 0, 0); + + for (i, entry) in dir_contents.iter().enumerate() { + let coord: (i32, i32) = (i as i32, 0); + + ncurses::wmove(win.win, coord.0, coord.1); + + let attr = if i + start == curr_index { + ncurses::A_STANDOUT() + } else { + 0 + }; + let attrs = get_theme_attr(attr, entry); + + draw_func(win, entry, attrs.0, coord); + + ncurses::mvwchgat(win.win, coord.0, coord.1, -1, attrs.1, attrs.2); + } + win.queue_for_refresh(); +} + +lazy_static! { + static ref USERCACHE: Mutex<UsersCache> = Mutex::new(UsersCache::new()); +} + +pub fn wprint_file_status( + win: &window::JoshutoPanel, + entry: &JoshutoDirEntry, + index: usize, + len: usize, +) { + wprint_file_mode(win.win, entry); + + ncurses::waddch(win.win, ' ' as ncurses::chtype); + ncurses::waddstr(win.win, &format!("{}/{} ", index + 1, len)); + + let usercache = USERCACHE.lock().unwrap(); + + match usercache.get_user_by_uid(entry.metadata.uid) { + Some(s) => match s.name().to_str() { + Some(name) => ncurses::waddstr(win.win, name), + None => ncurses::waddstr(win.win, "OsStr error"), + }, + None => ncurses::waddstr(win.win, "unknown user"), + }; + ncurses::waddch(win.win, ' ' as ncurses::chtype); + match usercache.get_group_by_gid(entry.metadata.gid) { + Some(s) => match s.name().to_str() { + Some(name) => ncurses::waddstr(win.win, name), + None => ncurses::waddstr(win.win, "OsStr error"), + }, + None => ncurses::waddstr(win.win, "unknown user"), + }; + + ncurses::waddstr(win.win, " "); + wprint_file_info(win.win, entry); + win.queue_for_refresh(); +} + +pub fn wprint_file_mode(win: ncurses::WINDOW, file: &JoshutoDirEntry) { + use std::os::unix::fs::PermissionsExt; + + let mode = file.metadata.permissions.mode(); + + ncurses::wattron(win, ncurses::COLOR_PAIR(6)); + ncurses::waddstr(win, unix::stringify_mode(mode).as_str()); + ncurses::wattroff(win, ncurses::COLOR_PAIR(6)); +} + +pub fn wprint_file_info(win: ncurses::WINDOW, file: &JoshutoDirEntry) { + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + + let mode = file.metadata.permissions.mode(); + + let mtime_string = file_mtime_to_string(file.metadata.modified); + ncurses::waddstr(win, &mtime_string); + ncurses::waddch(win, ' ' as ncurses::chtype); + + if file.file_path().is_dir() { + let is_link: u32 = libc::S_IFLNK as u32; + if mode >> 9 & is_link >> 9 == mode >> 9 { + if let Ok(path) = fs::read_link(&file.file_path()) { + ncurses::waddstr(win, " -> "); + ncurses::waddstr(win, path.to_str().unwrap()); + } + } + } else { + let file_size_string = file_size_to_string_detailed(file.metadata.len as f64); + ncurses::waddstr(win, &file_size_string); + } + + /* + ncurses::waddstr(win, " "); + if let Some(s) = tree_magic::from_filepath(&file.file_path()) { + ncurses::waddstr(win, &s); + } + */ + } +} + +pub fn redraw_tab_view(win: &window::JoshutoPanel, context: &JoshutoContext) { + let tab_len = context.tabs.len(); + ncurses::werase(win.win); + if tab_len == 1 { + } else if tab_len >= 6 { + ncurses::wmove(win.win, 0, 0); + ncurses::wattron(win.win, ncurses::A_BOLD() | ncurses::A_STANDOUT()); + ncurses::waddstr(win.win, &format!("{}", context.curr_tab_index + 1)); + ncurses::wattroff(win.win, ncurses::A_STANDOUT()); + ncurses::waddstr(win.win, &format!(" {}", tab_len)); + ncurses::wattroff(win.win, ncurses::A_BOLD()); + } else { + ncurses::wattron(win.win, ncurses::A_BOLD()); + for i in 0..tab_len { + if i == context.curr_tab_index { + ncurses::wattron(win.win, ncurses::A_STANDOUT()); + ncurses::waddstr(win.win, &format!("{} ", i + 1)); + ncurses::wattroff(win.win, ncurses::A_STANDOUT()); + } else { + ncurses::waddstr(win.win, &format!("{} ", i + 1)); + } + } + ncurses::wattroff(win.win, ncurses::A_BOLD()); + } + ncurses::wnoutrefresh(win.win); +} + +pub fn get_theme_attr( + mut attr: ncurses::attr_t, + entry: &JoshutoDirEntry, +) -> ((usize, &str), ncurses::attr_t, i16) { + use std::os::unix::fs::FileTypeExt; + use std::os::unix::fs::PermissionsExt; + + let theme: &JoshutoColorTheme; + let colorpair: i16; + + let file_type = &entry.metadata.file_type; + if entry.is_selected() { + theme = &THEME_T.selection; + colorpair = THEME_T.selection.colorpair; + } else if file_type.is_dir() { + theme = &THEME_T.directory; + colorpair = THEME_T.directory.colorpair; + } else if file_type.is_symlink() { + theme = &THEME_T.link; + colorpair = THEME_T.link.colorpair; + } else if file_type.is_block_device() + || file_type.is_char_device() + || file_type.is_fifo() + || file_type.is_socket() + { + theme = &THEME_T.socket; + colorpair = THEME_T.link.colorpair; + } else { + let mode = entry.metadata.permissions.mode(); + if unix::is_executable(mode) { + theme = &THEME_T.executable; + colorpair = THEME_T.executable.colorpair; + } else if let Some(ext) = entry.file_name().rfind('.') { + let extension: &str = &entry.file_name()[ext + 1..]; + if let Some(s) = THEME_T.ext.get(extension) { + theme = &s; + colorpair = theme.colorpair; + } else { + theme = &THEME_T.regular; + colorpair = theme.colorpair; + } + } else { + theme = &THEME_T.regular; + colorpair = theme.colorpair; + } + } + + if theme.bold { + attr |= ncurses::A_BOLD(); + } + if theme.underline { + attr |= ncurses::A_UNDERLINE(); + } + + let prefix = match theme.prefix.as_ref() { + Some(p) => (p.size(), p.prefix()), + None => (1, " "), + }; + + (prefix, attr, colorpair) +} + +fn file_size_to_string_detailed(mut file_size: f64) -> String { + const FILE_UNITS: [&str; 6] = ["B", "KB", "MB", "GB", "TB", "EB"]; + const CONV_RATE: f64 = 1024.0; + + let mut index = 0; + while file_size > CONV_RATE { + file_size /= CONV_RATE; + index += 1; + } + + if file_size >= 1000.0 { + format!("{:.0}{}", file_size, FILE_UNITS[index]) + } else if file_size >= 100.0 { + format!(" {:.0}{}", file_size, FILE_UNITS[index]) + } else if file_size >= 10.0 { + format!("{:.1}{}", file_size, FILE_UNITS[index]) + } else { + format!("{:.2}{}", file_size, FILE_UNITS[index]) + } +} + +fn file_mtime_to_string(mtime: time::SystemTime) -> String { + const MTIME_FORMATTING: &str = "%Y-%m-%d %H:%M"; + + let datetime: chrono::DateTime<chrono::offset::Utc> = mtime.into(); + datetime.format(MTIME_FORMATTING).to_string() +} + +fn file_size_to_string(mut file_size: f64) -> String { + const FILE_UNITS: [&str; 6] = ["B", "K", "M", "G", "T", "E"]; + const CONV_RATE: f64 = 1024.0; + + let mut index = 0; + while file_size > CONV_RATE { + file_size /= CONV_RATE; + index += 1; + } + + if file_size >= 100.0 { + format!(" {:.0} {}", file_size, FILE_UNITS[index]) + } else if file_size >= 10.0 { + format!(" {:.1} {}", file_size, FILE_UNITS[index]) + } else { + format!(" {:.2} {}", file_size, FILE_UNITS[index]) + } +} |