diff options
author | Jiayi Zhao <jeff.no.zhao@gmail.com> | 2020-02-22 19:54:41 -0500 |
---|---|---|
committer | Jiayi Zhao <jeff.no.zhao@gmail.com> | 2020-02-22 19:54:41 -0500 |
commit | d38dcdbbee44187bdb605dbf9bbf9c6c6d3e4f35 (patch) | |
tree | 91b059b99793fe250c67f66be3930a298117e0b3 /src | |
parent | 03594099dafb4cda04e50f087df61cf76e2034d0 (diff) |
implement textfield widget
- for asking users for long input strings
- implement prompt widget
- for prompting users for a single key response
Diffstat (limited to 'src')
-rw-r--r-- | src/commands/command_line.rs | 7 | ||||
-rw-r--r-- | src/commands/delete_files.rs | 91 | ||||
-rw-r--r-- | src/commands/rename_file.rs | 4 | ||||
-rw-r--r-- | src/commands/set_mode.rs | 2 | ||||
-rw-r--r-- | src/context.rs | 6 | ||||
-rw-r--r-- | src/fs/dirlist.rs | 4 | ||||
-rw-r--r-- | src/fs/entry.rs | 16 | ||||
-rw-r--r-- | src/fs/metadata.rs | 6 | ||||
-rw-r--r-- | src/main.rs | 2 | ||||
-rw-r--r-- | src/run.rs | 56 | ||||
-rw-r--r-- | src/ui/widgets/mod.rs | 2 | ||||
-rw-r--r-- | src/ui/widgets/tui_prompt.rs | 60 | ||||
-rw-r--r-- | src/ui/widgets/tui_textfield.rs | 176 | ||||
-rw-r--r-- | src/ui/widgets/tui_view.rs | 40 | ||||
-rw-r--r-- | src/util/format.rs | 2 | ||||
-rw-r--r-- | src/util/mod.rs | 1 | ||||
-rw-r--r-- | src/util/unix.rs (renamed from src/unix.rs) | 5 |
17 files changed, 355 insertions, 125 deletions
diff --git a/src/commands/command_line.rs b/src/commands/command_line.rs index a00f442..f44c4a5 100644 --- a/src/commands/command_line.rs +++ b/src/commands/command_line.rs @@ -23,8 +23,11 @@ impl CommandLine { context: &mut JoshutoContext, backend: &mut TuiBackend, ) -> JoshutoResult<()> { - // let mut textfield = TuiTextField::new(backend, &context.events); - let user_input: Option<String> = None; // textfield.readline(); + let mut textfield = TuiTextField::default() + .prompt(":") + .prefix(self.prefix.as_str()) + .suffix(self.suffix.as_str()); + let user_input: Option<String> = textfield.get_input(backend, &context); if let Some(s) = user_input { let trimmed = s.trim_start(); diff --git a/src/commands/delete_files.rs b/src/commands/delete_files.rs index 2219283..78344d1 100644 --- a/src/commands/delete_files.rs +++ b/src/commands/delete_files.rs @@ -2,8 +2,7 @@ use std::fs; use std::io::{self, Write}; use std::path; -use termion::clear; -use termion::cursor::Goto; +use termion::event::Key; use crate::commands::{JoshutoCommand, JoshutoRunnable, ReloadDirList}; use crate::context::JoshutoContext; @@ -11,6 +10,8 @@ use crate::error::JoshutoResult; use crate::ui::TuiBackend; use crate::util::event::Event; +use crate::ui::widgets::TuiPrompt; + #[derive(Clone, Debug)] pub struct DeleteFiles; @@ -36,84 +37,46 @@ impl DeleteFiles { } fn delete_files(context: &mut JoshutoContext, backend: &mut TuiBackend) -> std::io::Result<()> { - let curr_tab = &mut context.tabs[context.curr_tab_index]; + let curr_tab = &context.tabs[context.curr_tab_index]; let paths = match curr_tab.curr_list_ref() { Some(s) => s.get_selected_paths(), None => Vec::new(), }; + let paths_len = paths.len(); - if paths.is_empty() { + if paths_len == 0 { return Err(std::io::Error::new( std::io::ErrorKind::Other, "no files selected", )); } - let frame = backend.terminal.get_frame(); - let f_size = frame.size(); - - let termion_terminal = backend.terminal.backend_mut(); - - write!( - termion_terminal, - "{}Delete {} files? (y/N){}", - Goto(1, f_size.height), - paths.len(), - clear::AfterCursor - ); - - io::stdout().flush().ok(); - - let mut ch = termion::event::Key::Char('n'); - while let Ok(evt) = context.events.next() { - match evt { - Event::Input(key) => { - if key == termion::event::Key::Char('y') - || key == termion::event::Key::Char('\n') - { - ch = termion::event::Key::Char('y'); - } - break; - } - _ => {} - } - } - - if ch == termion::event::Key::Char('y') { - if paths.len() > 1 { - write!( - termion_terminal, - "{}Are you sure? (Y/n){}", - Goto(1, f_size.height), - clear::AfterCursor - ); - - io::stdout().flush().ok(); + let ch = { + let prompt_str = format!("Delete {} files? (y/N)", paths_len); + let mut prompt = TuiPrompt::new(&prompt_str); + prompt.get_key(backend, &context) + }; - while let Ok(evt) = context.events.next() { - match evt { - Event::Input(key) => { - ch = key; - break; - } - _ => {} - } + if ch == Key::Char('y') { + if paths_len > 1 { + let ch = { + let prompt_str = "Are you sure? (Y/n)"; + let mut prompt = TuiPrompt::new(prompt_str); + prompt.get_key(backend, &context) + }; + if ch == Key::Char('y') || ch == Key::Char('\n') { + Self::remove_files(&paths)?; + ReloadDirList::reload(context.curr_tab_index, context)?; + let msg = format!("Deleted {} files", paths_len); + context.message_queue.push_back(msg); } } else { - ch = termion::event::Key::Char('y'); + Self::remove_files(&paths)?; + ReloadDirList::reload(context.curr_tab_index, context)?; + let msg = format!("Deleted {} files", paths_len); + context.message_queue.push_back(msg); } } - - if ch == termion::event::Key::Char('y') { - Self::remove_files(&paths)?; - ReloadDirList::reload(context.curr_tab_index, context)?; - } - write!( - termion_terminal, - "{}{}", - Goto(1, f_size.height), - clear::AfterCursor - ); Ok(()) } } diff --git a/src/commands/rename_file.rs b/src/commands/rename_file.rs index dad9632..763a638 100644 --- a/src/commands/rename_file.rs +++ b/src/commands/rename_file.rs @@ -84,8 +84,8 @@ impl RenameFileAppend { let prefix; let suffix; if let Some(ext) = file_name.rfind('.') { - prefix = format!("rename {}", &file_name[0..ext]); - suffix = String::from(&file_name[ext..]); + prefix = format!("rename {}", &file_name[0..ext + 1]); + suffix = String::from(&file_name[ext + 1..]); } else { prefix = format!("rename {}", file_name); suffix = String::new(); diff --git a/src/commands/set_mode.rs b/src/commands/set_mode.rs index 8a974e9..94a6d23 100644 --- a/src/commands/set_mode.rs +++ b/src/commands/set_mode.rs @@ -4,7 +4,7 @@ use crate::error::JoshutoResult; use crate::fs::JoshutoDirEntry; use crate::ui::widgets::TuiTextField; use crate::ui::TuiBackend; -use crate::unix; +use crate::util::unix; #[derive(Clone, Debug)] pub struct SetMode; diff --git a/src/context.rs b/src/context.rs index c0651d9..328f9f6 100644 --- a/src/context.rs +++ b/src/context.rs @@ -6,7 +6,7 @@ use crate::io::IOWorkerThread; use crate::tab::JoshutoTab; use crate::util::event::Events; -pub const MESSAGE_VISIBLE_DURATION: usize = 2; +pub const MESSAGE_VISIBLE_DURATION: usize = 1; pub struct JoshutoContext { pub exit: bool, @@ -15,6 +15,7 @@ pub struct JoshutoContext { pub worker_queue: VecDeque<IOWorkerThread>, pub trx: (mpsc::SyncSender<u64>, mpsc::Receiver<u64>), + pub worker_msg: Option<String>, pub message_queue: VecDeque<String>, pub message_elapse: usize, pub events: Events, @@ -30,8 +31,9 @@ impl JoshutoContext { tabs: Vec::new(), worker_queue: VecDeque::with_capacity(10), trx: mpsc::sync_channel::<u64>(1), + worker_msg: None, message_queue: VecDeque::with_capacity(4), - message_elapse: MESSAGE_VISIBLE_DURATION, + message_elapse: 0, events: Events::new(), config_t, diff --git a/src/fs/dirlist.rs b/src/fs/dirlist.rs index 09c752c..cf53457 100644 --- a/src/fs/dirlist.rs +++ b/src/fs/dirlist.rs @@ -2,7 +2,6 @@ use std::{fs, path}; use crate::fs::{JoshutoDirEntry, JoshutoMetadata}; use crate::sort::SortOption; -use crate::window::JoshutoPageState; #[derive(Debug)] pub struct JoshutoDirList { @@ -12,7 +11,6 @@ pub struct JoshutoDirList { order_outdated: bool, pub metadata: JoshutoMetadata, pub contents: Vec<JoshutoDirEntry>, - pub pagestate: JoshutoPageState, } impl JoshutoDirList { @@ -25,7 +23,6 @@ impl JoshutoDirList { let index = if contents.is_empty() { None } else { Some(0) }; let metadata = JoshutoMetadata::from(&path)?; - let pagestate = JoshutoPageState::default(); Ok(JoshutoDirList { index, @@ -34,7 +31,6 @@ impl JoshutoDirList { order_outdated: false, metadata, contents, - pagestate, }) } diff --git a/src/fs/entry.rs b/src/fs/entry.rs index 5cb8a2b..84e818c 100644 --- a/src/fs/entry.rs +++ b/src/fs/entry.rs @@ -4,6 +4,7 @@ use tui::style::{Color, Modifier, Style}; use crate::fs::JoshutoMetadata; +use crate::util::unix; use crate::THEME_T; #[derive(Clone, Debug)] @@ -117,6 +118,8 @@ impl JoshutoDirEntry { let metadata = &self.metadata; let filetype = metadata.file_type; + use std::os::unix::fs::MetadataExt; + let mut modifier = Modifier::empty(); if filetype.is_dir() { @@ -156,6 +159,8 @@ impl JoshutoDirEntry { } pub fn get_style(&self) -> Style { + use std::os::unix::fs::MetadataExt; + let metadata = &self.metadata; let filetype = metadata.file_type; @@ -194,6 +199,17 @@ impl JoshutoDirEntry { style = style.fg(THEME_T.link.fg).bg(THEME_T.link.bg); style = style.modifier(modifier); + } else if unix::is_executable(metadata.mode) { + let mut modifier = Modifier::empty(); + if THEME_T.link.bold { + modifier.insert(Modifier::BOLD); + } + if THEME_T.link.underline { + modifier.insert(Modifier::UNDERLINED); + } + + style = style.fg(THEME_T.executable.fg).bg(THEME_T.executable.bg); + style = style.modifier(modifier); } else { match self.file_path().extension() { None => {} diff --git a/src/fs/metadata.rs b/src/fs/metadata.rs index f3fea53..dbf4d37 100644 --- a/src/fs/metadata.rs +++ b/src/fs/metadata.rs @@ -10,6 +10,8 @@ pub struct JoshutoMetadata { pub uid: u32, #[cfg(unix)] pub gid: u32, + #[cfg(unix)] + pub mode: u32, } impl JoshutoMetadata { @@ -28,6 +30,8 @@ impl JoshutoMetadata { let uid = metadata.uid(); #[cfg(unix)] let gid = metadata.gid(); + #[cfg(unix)] + let mode = metadata.mode(); Ok(JoshutoMetadata { len, @@ -38,6 +42,8 @@ impl JoshutoMetadata { uid, #[cfg(unix)] gid, + #[cfg(unix)] + mode, }) } } diff --git a/src/main.rs b/src/main.rs index d50927e..a2a01be 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,9 +9,7 @@ mod run; mod sort; mod tab; mod ui; -mod unix; mod util; -mod window; use lazy_static::lazy_static; use std::path::PathBuf; @@ -2,7 +2,7 @@ use std::thread; use crate::commands::{CommandKeybind, CursorMoveUp, JoshutoRunnable}; use crate::config::{JoshutoCommandMapping, JoshutoConfig}; -use crate::context::JoshutoContext; +use crate::context::{JoshutoContext, MESSAGE_VISIBLE_DURATION}; use crate::tab::JoshutoTab; use crate::ui; use crate::ui::widgets::{TuiCommandMenu, TuiView}; @@ -56,9 +56,7 @@ pub fn run(config_t: JoshutoConfig, keymap_t: JoshutoCommandMapping) -> std::io: Ok(event) => { match event { Event::IOWorkerProgress(p) => { - context - .message_queue - .push_back(format!("bytes copied {}", p)); + context.worker_msg = Some(format!("bytes copied {}", p)); } Event::IOWorkerResult => { match io_handle { @@ -71,33 +69,45 @@ pub fn run(config_t: JoshutoConfig, keymap_t: JoshutoCommandMapping) -> std::io: None => {} } io_handle = None; + context.worker_msg = None; } - Event::Input(key) => match keymap_t.get(&key) { - None => { - context - .message_queue - .push_back(format!("Unknown keycode: {:?}", key)); - } - Some(CommandKeybind::SimpleKeybind(command)) => { - if let Err(e) = command.execute(&mut context, &mut backend) { - context.message_queue.push_back(e.to_string()); + Event::Input(key) => { + /* Message handling */ + if !context.message_queue.is_empty() { + if context.message_elapse < MESSAGE_VISIBLE_DURATION { + context.message_elapse += 1; + } else { + let _ = context.message_queue.pop_front(); + context.message_elapse = 0; } } - Some(CommandKeybind::CompositeKeybind(m)) => { - let mut map: &JoshutoCommandMapping = &m; - - let cmd = { - let mut menu = TuiCommandMenu::new(); - menu.get_input(&mut backend, &context, map) - }; - - if let Some(command) = cmd { + match keymap_t.get(&key) { + None => { + context + .message_queue + .push_back(format!("Unknown keycode: {:?}", key)); + } + Some(CommandKeybind::SimpleKeybind(command)) => { if let Err(e) = command.execute(&mut context, &mut backend) { context.message_queue.push_back(e.to_string()); } } + Some(CommandKeybind::CompositeKeybind(m)) => { + let mut map: &JoshutoCommandMapping = &m; + + let cmd = { + let mut menu = TuiCommandMenu::new(); + menu.get_input(&mut backend, &context, map) + }; + + if let Some(command) = cmd { + if let Err(e) = command.execute(&mut context, &mut backend) { + context.message_queue.push_back(e.to_string()); + } + } + } } - }, + } } let mut view = TuiView::new(&context); backend.render(&mut view); diff --git a/src/ui/widgets/mod.rs b/src/ui/widgets/mod.rs index e36c904..49fbfc5 100644 --- a/src/ui/widgets/mod.rs +++ b/src/ui/widgets/mod.rs @@ -2,6 +2,7 @@ pub mod tui_dirlist; pub mod tui_dirlist_detailed; pub mod tui_footer; pub mod tui_menu; +pub mod tui_prompt; pub mod tui_textfield; pub mod tui_topbar; pub mod tui_view; @@ -10,6 +11,7 @@ pub use self::tui_dirlist::TuiDirList; pub use self::tui_dirlist_detailed::TuiDirListDetailed; pub use self::tui_footer::TuiFooter; pub use self::tui_menu::{TuiCommandMenu, TuiMenu}; +pub use self::tui_prompt::TuiPrompt; pub use self::tui_textfield::TuiTextField; pub use self::tui_topbar::TuiTopBar; pub use self::tui_view::TuiView; diff --git a/src/ui/widgets/tui_prompt.rs b/src/ui/widgets/tui_prompt.rs new file mode 100644 index 0000000..21526f3 --- /dev/null +++ b/src/ui/widgets/tui_prompt.rs @@ -0,0 +1,60 @@ +use termion::event::Key; +use tui::layout::Rect; +use tui::style::{Color, Style}; +use tui::widgets::{Block, Borders, List, Paragraph, Text, Widget}; +use unicode_width::UnicodeWidthStr; + +use crate::context::JoshutoContext; +use crate::ui::TuiBackend; +use crate::util::event::{Event, Events}; + +use super::TuiView; + +pub struct TuiPrompt<'a> { + prompt: &'a str, +} + +impl<'a> TuiPrompt<'a> { + pub fn new(prompt: &'a str) -> Self { + Self { prompt } + } + + pub fn get_key(&mut self, backend: &mut TuiBackend, context: &JoshutoContext) -> Key { + loop { + backend.terminal.draw(|mut frame| { + let f_size = frame.size(); + if f_size.height == 0 { + return; + } + + { + let mut view = TuiView::new(&context); + view.show_bottom_status = false; + view.render(&mut frame, f_size); + } + + let prompt_style = Style::default().fg(Color::LightYellow); + + let text = [Text::styled(self.prompt, prompt_style)]; + + let textfield_rect = Rect { + x: 0, + y: f_size.height - 1, + width: f_size.width, + height: 1, + }; + + Paragraph::new(text.iter()) + .wrap(true) + .render(&mut frame, textfield_rect); + }); + + if let Ok(event) = context.events.next() { + match event { + Event::Input(key) => return key, + _ => {} + }; + } + } + } +} diff --git a/src/ui/widgets/tui_textfield.rs b/src/ui/widgets/tui_textfield.rs index c0f54a8..9dbbad3 100644 --- a/src/ui/widgets/tui_textfield.rs +++ b/src/ui/widgets/tui_textfield.rs @@ -1,9 +1,11 @@ +use std::io::Write; + use rustyline::completion::{Candidate, Completer, FilenameCompleter, Pair}; use rustyline::line_buffer; -use termion::clear; use termion::cursor::Goto; use termion::event::Key; +use tui::backend::Backend; use tui::layout::Rect; use tui::style::{Color, Style}; use tui::widgets::{Block, Borders, List, Paragraph, Text, Widget}; @@ -13,7 +15,7 @@ use crate::context::JoshutoContext; use crate::ui::TuiBackend; use crate::util::event::{Event, Events}; -use super::TuiMenu; +use super::{TuiMenu, TuiView}; struct CompletionTracker { pub index: usize, @@ -34,12 +36,31 @@ impl CompletionTracker { } pub struct TuiTextField<'a> { - menu: Option<&'a mut TuiMenu<'a>>, + _prompt: &'a str, + _prefix: &'a str, + _suffix: &'a str, + _menu: Option<&'a mut TuiMenu<'a>>, } impl<'a> TuiTextField<'a> { - pub fn new(menu: &'a mut TuiMenu<'a>) -> Self { - Self { menu: Some(menu) } + pub fn menu(mut self, menu: &'a mut TuiMenu<'a>) -> Self { + self._menu = Some(menu); + self + } + + pub fn prompt(mut self, prompt: &'a str) -> Self { + self._prompt = prompt; + self + } + + pub fn prefix(mut self, prefix: &'a str) -> Self { + self._prefix = prefix; + self + } + + pub fn suffix(mut self, suffix: &'a str) -> Self { + self._suffix = suffix; + self } pub fn get_input( @@ -47,11 +68,44 @@ impl<'a> TuiTextField<'a> { backend: &mut TuiBackend, context: &JoshutoContext, ) -> Option<String> { - let mut input_string = String::with_capacity(64); + let mut line_buffer = line_buffer::LineBuffer::with_capacity(255); + let completer = FilenameCompleter::new(); + + let mut completion_tracker: Option<CompletionTracker> = None; + + let mut char_idx = self + ._prefix + .char_indices() + .last() + .map(|(i, c)| i) + .unwrap_or(0); + + line_buffer.insert_str(0, self._prefix); + line_buffer.insert_str(line_buffer.len(), self._suffix); + line_buffer.set_pos(char_idx); + + backend.terminal.show_cursor(); + let mut cursor_xpos = line_buffer.pos() + 1; + { + let frame = backend.terminal.get_frame(); + let f_size = frame.size(); + backend + .terminal + .set_cursor(cursor_xpos as u16, f_size.height - 1); + } loop { backend.terminal.draw(|mut frame| { let f_size = frame.size(); + if f_size.height == 0 { + return; + } + + { + let mut view = TuiView::new(&context); + view.show_bottom_status = false; + view.render(&mut frame, f_size); + } let top_rect = Rect { x: 0, @@ -60,27 +114,129 @@ impl<'a> TuiTextField<'a> { height: 1, }; - if let Some(menu) = self.menu.as_mut() { + if let Some(menu) = self._menu.as_mut() { menu.render(&mut frame, top_rect); } + + let cmd_prompt_style = Style::default().fg(Color::LightGreen); + + let text = [ + Text::styled(self._prompt, cmd_prompt_style), + Text::raw(line_buffer.as_str()), + ]; + + let textfield_rect = Rect { + x: 0, + y: f_size.height - 1, + width: f_size.width, + height: 1, + }; + + Paragraph::new(text.iter()) + .wrap(true) + .render(&mut frame, textfield_rect); }); if let Ok(event) = context.events.next() { match event { + Event::Input(Key::Backspace) => { + if line_buffer.backspace(1) { + completion_tracker.take(); + } + } + Event::Input(Key::Left) => { + if line_buffer.move_backward(1) { + completion_tracker.take(); + } + } + Event::Input(Key::Right) => { + if line_buffer.move_forward(1) { + completion_tracker.take(); + } + } + Event::Input(Key::Delete) => { + if line_buffer.delete(1).is_some() { + completion_tracker.take(); + } + } + Event::Input(Key::Home) => { + line_buffer.move_end(); + completion_tracker.take(); + } + Event::Input(Key::End) => { + line_buffer.move_end(); + completion_tracker.take(); + } + Event::Input(Key::Up) => {} + Event::Input(Key::Down) => {} Event::Input(Key::Esc) => { + backend.terminal.hide_cursor(); return None; } + Event::Input(Key::Char('\t')) => { + if completion_tracker.is_none() { + let res = + completer.complete_path(line_buffer.as_str(), line_buffer.pos()); + if let Ok((pos, mut candidates)) = res { + candidates.sort_by(|x, y| { + x.display() + .partial_cmp(y.display()) + .unwrap_or(std::cmp::Ordering::Less) + }); + let ct = CompletionTracker::new( + pos, + candidates, + String::from(line_buffer.as_str()), + ); + completion_tracker = Some(ct); + } + } + + if let Some(ref mut s) = completion_tracker { + if s.index < s.candidates.len() { + let candidate = &s.candidates[s.index]; + completer.update(&mut line_buffer, s.pos, candidate.replacement()); + s.index += 1; + } + } + } Event::Input(Key::Char('\n')) => { break; } Event::Input(Key::Char(c)) => { - input_string.push(c); + if line_buffer.insert(c, 1).is_some() { + completion_tracker.take(); + } } _ => {} }; } + cursor_xpos = line_buffer.pos() + 1; + { + let frame = backend.terminal.get_frame(); + let f_size = frame.size(); + backend + .terminal + .set_cursor(cursor_xpos as u16, f_size.height - 1); + } + } + backend.terminal.hide_cursor(); + if line_buffer.as_str().is_empty() { + None + } else { + let strin = line_buffer.to_string(); + Some(strin) + } + } +} + +impl<'a> std::default::Default for TuiTextField<'a> { + fn default() -> Self { + Self { + _prompt: "", + _prefix: "", + _suffix: "", + _menu: None, } - eprintln!("You typed: {}", input_string); - Some(input_string) } } diff --git a/src/ui/widgets/tui_view.rs b/src/ui/widgets/tui_view.rs index 57f644c..0f2d64d 100644 --- a/src/ui/widgets/tui_view.rs +++ b/src/ui/widgets/tui_view.rs @@ -1,6 +1,7 @@ use tui::buffer::Buffer; use tui::layout::{Direction, Layout, Rect}; -use tui::widgets::Widget; +use tui::style::{Color, Modifier, Style}; +use tui::widgets::{Paragraph, Text, Widget}; use unicode_width::UnicodeWidthStr; use super::{TuiDirList, TuiDirListDetailed, TuiFooter, TuiTopBar}; @@ -8,13 +9,17 @@ use crate::context::JoshutoContext; pub struct TuiView<'a> { pub context: &'a JoshutoContext, + pub show_bottom_status: bool, } use super::super::{DEFAULT_LAYOUT, NO_PREVIEW_LAYOUT}; impl<'a> TuiView<'a> { pub fn new(context: &'a JoshutoContext) -> Self { - Self { context } + Self { + context, + show_bottom_status: true, + } } } @@ -55,15 +60,30 @@ impl<'a> Widget for TuiView<'a> { if let Some(curr_list) = curr_list.as_ref() { TuiDirListDetailed::new(&curr_list).draw(layout_rect[1], buf); + let rect = Rect { + x: 0, + y: f_size.height - 1, + width: f_size.width, + height: 1, + }; + + let message_style = Style::default() + .fg(Color::LightCyan) + .modifier(Modifier::BOLD); + + if self.show_bottom_status { + /* draw the bottom status bar */ + if let Some(msg) = self.context.worker_msg.as_ref() { + let text = [Text::styled(msg, message_style)]; + + Paragraph::new(text.iter()).wrap(true).draw(rect, buf); + } else if !self.context.message_queue.is_empty() { + let text = [Text::styled(&self.context.message_queue[0], message_style)]; - if let Some(entry) = curr_list.get_curr_ref() { - let rect = Rect { - x: 0, - y: f_size.height - 1, - width: f_size.width, - height: 1, - }; - TuiFooter::new(entry).draw(rect, buf); + Paragraph::new(text.iter()).wrap(true).draw(rect, buf); + } else if let Some(entry) = curr_list.get_curr_ref() { + TuiFooter::new(entry).draw(rect, buf); + } } }; diff --git a/src/util/format.rs b/src/util/format.rs index 144aaaf..53fbf5f 100644 --- a/src/util/format.rs +++ b/src/util/format.rs @@ -1,6 +1,6 @@ use std::time; -use crate::unix; +use super::unix; pub fn file_size_to_string(mut file_size: f64) -> String { const FILE_UNITS: [&str; 6] = ["B", "K", "M", "G", "T", "E"]; diff --git a/src/util/mod.rs b/src/util/mod.rs index 1ec901b..9f0a3b6 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -2,3 +2,4 @@ pub mod event; pub mod format; pub mod key_mapping; pub mod load_child; +pub mod unix; diff --git a/src/unix.rs b/src/util/unix.rs index 1c25cd2..2113fc1 100644 --- a/src/unix.rs +++ b/src/util/unix.rs @@ -3,10 +3,7 @@ use std::path::Path; pub fn is_executable(mode: u32) -> bool { const LIBC_PERMISSION_VALS: [libc::mode_t; 3] = [libc::S_IXUSR, libc::S_IXGRP, libc::S_IXOTH]; - LIBC_PERMISSION_VALS.iter().any(|val| { - let val: u32 = (*val) as u32; - mode & val != 0 - }) + LIBC_PERMISSION_VALS.iter().any(|val| mode & *val != 0) } pub fn stringify_mode(mode: u32) -> String { |