diff options
Diffstat (limited to 'src/term_manager.rs')
-rw-r--r-- | src/term_manager.rs | 277 |
1 files changed, 180 insertions, 97 deletions
diff --git a/src/term_manager.rs b/src/term_manager.rs index 330906d..cc225ef 100644 --- a/src/term_manager.rs +++ b/src/term_manager.rs @@ -13,12 +13,11 @@ use crate::completion::InputCompleted; use crate::compress::CompressionMethod; use crate::config::Colors; use crate::constant_strings_paths::{ - CHMOD_LINES, FILTER_LINES, HELP_FIRST_SENTENCE, HELP_SECOND_SENTENCE, LOG_FIRST_SENTENCE, - LOG_SECOND_SENTENCE, NEWDIR_LINES, NEWFILE_LINES, NVIM_ADDRESS_LINES, PASSWORD_LINES, - REGEX_LINES, RENAME_LINES, SHELL_LINES, SORT_LINES, + HELP_FIRST_SENTENCE, HELP_SECOND_SENTENCE, LOG_FIRST_SENTENCE, LOG_SECOND_SENTENCE, }; use crate::content_window::ContentWindow; use crate::fileinfo::{fileinfo_attr, shorten_path, FileInfo}; +use crate::log::read_last_log_line; use crate::mode::{InputSimple, MarkAction, Mode, Navigate, NeedConfirmation}; use crate::mount_help::MountHelper; use crate::preview::{Preview, TextKind, Window}; @@ -83,6 +82,11 @@ impl EventReader { pub fn poll_event(&self) -> Result<Event> { Ok(self.term.poll_event()?) } + + /// Height of the current terminal + pub fn term_height(&self) -> Result<usize> { + Ok(self.term.term_size()?.1) + } } macro_rules! impl_preview { @@ -94,19 +98,42 @@ macro_rules! impl_preview { }; } +/// Bunch of attributes describing the state of a main window +/// relatively to other windows +struct WinMainAttributes { + /// horizontal position, in cells + x_position: usize, + /// is this the first (false) or second (true) window ? + is_second: bool, + /// is this tab selected ? + is_selected: bool, + /// is there a secondary window ? + has_window_below: bool, +} + +impl WinMainAttributes { + fn new(x_position: usize, is_second: bool, is_selected: bool, has_window_below: bool) -> Self { + Self { + x_position, + is_second, + is_selected, + has_window_below, + } + } +} + struct WinMain<'a> { status: &'a Status, tab: &'a Tab, disk_space: &'a str, colors: &'a Colors, - x_position: usize, - is_second: bool, + attributes: WinMainAttributes, } impl<'a> Draw for WinMain<'a> { fn draw(&self, canvas: &mut dyn Canvas) -> DrawResult<()> { canvas.clear()?; - if self.status.dual_pane && self.is_second && self.status.preview_second { + if self.status.dual_pane && self.attributes.is_second && self.status.preview_second { self.preview_as_second_pane(canvas)?; return Ok(()); } @@ -134,16 +161,14 @@ impl<'a> WinMain<'a> { index: usize, disk_space: &'a str, colors: &'a Colors, - abs: usize, - is_second: bool, + attributes: WinMainAttributes, ) -> Self { Self { status, tab: &status.tabs[index], disk_space, colors, - x_position: abs, - is_second, + attributes, } } @@ -151,7 +176,7 @@ impl<'a> WinMain<'a> { let tab = &self.status.tabs[0]; let (_, height) = canvas.size()?; self.preview(tab, &tab.preview.window_for_second_pane(height), canvas)?; - draw_colored_strings(0, 0, self.default_preview_first_line(tab), canvas)?; + draw_colored_strings(0, 0, self.default_preview_first_line(tab), canvas, false)?; Ok(()) } @@ -161,24 +186,29 @@ impl<'a> WinMain<'a> { /// When a confirmation is needed we ask the user to input `'y'` or /// something else. /// Returns the result of the number of printed chars. + /// The colors are reversed when the tab is selected. It gives a visual indication of where he is. fn first_line(&self, tab: &Tab, disk_space: &str, canvas: &mut dyn Canvas) -> Result<()> { - draw_colored_strings(0, 0, self.create_first_row(tab, disk_space)?, canvas) + draw_colored_strings( + 0, + 0, + self.create_first_row(tab, disk_space)?, + canvas, + self.attributes.is_selected, + ) } fn second_line(&self, status: &Status, tab: &Tab, canvas: &mut dyn Canvas) -> Result<usize> { match tab.mode { - Mode::Normal => { + Mode::Normal | Mode::Tree => { if !status.display_full { - let Some(file) = tab.selected() else { return Ok(0) }; + let Some(file) = tab.selected() else { + return Ok(0); + }; self.second_line_detailed(file, canvas) } else { self.second_line_simple(status, canvas) } } - Mode::Tree => { - let Some(file) = tab.selected() else { return Ok(0) }; - self.second_line_detailed(file, canvas) - } _ => Ok(0), } } @@ -202,21 +232,52 @@ impl<'a> WinMain<'a> { fn normal_first_row(&self, disk_space: &str) -> Result<Vec<String>> { Ok(vec![ - format!("{} ", shorten_path(&self.tab.path_content.path, None)?), - format!("{} files ", self.tab.path_content.true_len()), + format!(" {}", shorten_path(&self.tab.path_content.path, None)?), + self.first_row_filename(), + self.first_row_position(), format!("{} ", self.tab.path_content.used_space()), - format!("Avail: {disk_space} "), - format!("{} ", &self.tab.path_content.git_string()?), - format!("{} flags ", &self.status.flagged.len()), - format!("{}", &self.tab.path_content.sort_kind), + format!(" Avail: {disk_space} "), + format!(" {} ", self.tab.path_content.git_string()?), + self.first_row_flags(), + format!(" {} ", &self.tab.path_content.sort_kind), ]) } + fn first_row_filename(&self) -> String { + match self.tab.mode { + Mode::Tree => "".to_owned(), + _ => { + if let Some(fileinfo) = self.tab.path_content.selected() { + fileinfo.filename_without_dot_dotdot() + } else { + "".to_owned() + } + } + } + } + + fn first_row_position(&self) -> String { + format!( + " {} / {} ", + self.tab.path_content.index + 1, + self.tab.path_content.true_len() + 2 + ) + } + + fn first_row_flags(&self) -> String { + let nb_flagged = self.status.flagged.len(); + let flag_string = if self.status.flagged.len() > 1 { + "flags" + } else { + "flag" + }; + format!(" {nb_flagged} {flag_string} ",) + } + fn help_first_row() -> Vec<String> { - let version = std::env!("CARGO_PKG_VERSION"); vec![ HELP_FIRST_SENTENCE.to_owned(), - format!("Version: {version} "), + format!(" Version: {v} ", v = std::env!("CARGO_PKG_VERSION")), HELP_SECOND_SENTENCE.to_owned(), ] } @@ -231,13 +292,11 @@ impl<'a> WinMain<'a> { fn default_preview_first_line(&self, tab: &Tab) -> Vec<String> { match tab.path_content.selected() { Some(fileinfo) => { - let mut strings = vec![ - "Preview ".to_owned(), - format!("{}", fileinfo.path.to_string_lossy()), - ]; + let mut strings = vec![" Preview ".to_owned()]; if !tab.preview.is_empty() { - strings.push(format!(" {} / {}", tab.window.bottom, tab.preview.len())); + strings.push(format!(" {} / {} ", tab.window.bottom, tab.preview.len())); }; + strings.push(format!(" {} ", fileinfo.path.to_string_lossy())); strings } None => vec!["".to_owned()], @@ -287,7 +346,7 @@ impl<'a> WinMain<'a> { .content .iter() .enumerate() - .take(min(len, tab.window.bottom + 1)) + .take(min(len, tab.window.bottom)) .skip(tab.window.top) { let row = i + ContentWindow::WINDOW_MARGIN_TOP - tab.window.top; @@ -303,22 +362,41 @@ impl<'a> WinMain<'a> { canvas.print_with_attr(row, 0, &string, attr)?; } self.second_line(status, tab, canvas)?; + if !self.attributes.has_window_below { + self.log_line(canvas)?; + } Ok(()) } - fn tree(&self, status: &Status, tab: &Tab, canvas: &mut dyn Canvas) -> Result<()> { - let line_number_width = 3; + fn log_line(&self, canvas: &mut dyn Canvas) -> Result<()> { + let (_, height) = canvas.size()?; + canvas.print_with_attr(height - 1, 4, &read_last_log_line(), ATTR_YELLOW_BOLD)?; + Ok(()) + } + fn tree(&self, status: &Status, tab: &Tab, canvas: &mut dyn Canvas) -> Result<()> { + let left_margin = if status.display_full { 0 } else { 3 }; let (_, height) = canvas.size()?; let (top, bottom, len) = tab.directory.calculate_tree_window(height); - for (i, (prefix, colored_string)) in tab.directory.window(top, bottom, len) { + + for (i, (metadata, prefix, colored_string)) in tab.directory.window(top, bottom, len) { let row = i + ContentWindow::WINDOW_MARGIN_TOP - top; - let col = canvas.print(row, line_number_width, prefix)?; let mut attr = colored_string.attr; if status.flagged.contains(&colored_string.path) { attr.effect |= Effect::BOLD | Effect::UNDERLINE; } - canvas.print_with_attr(row, line_number_width + col + 1, &colored_string.text, attr)?; + let col_metadata = if status.display_full { + canvas.print_with_attr(row, left_margin, &metadata.text, attr)? + } else { + 0 + }; + let col_tree_prefix = canvas.print(row, left_margin + col_metadata, prefix)?; + canvas.print_with_attr( + row, + left_margin + col_metadata + col_tree_prefix + 1, + &colored_string.text, + attr, + )?; } self.second_line(status, tab, canvas)?; Ok(()) @@ -375,14 +453,14 @@ impl<'a> WinMain<'a> { Preview::Ueberzug(image) => { let (width, height) = canvas.size()?; image.ueberzug( - self.x_position as u16 + 2, + self.attributes.x_position as u16 + 2, 3, width as u16 - 2, height as u16 - 2, ); } Preview::Directory(directory) => { - for (i, (prefix, colored_string)) in + for (i, (_, prefix, colored_string)) in (directory).window(window.top, window.bottom, length) { let row = calc_line_row(i, window); @@ -422,6 +500,15 @@ impl<'a> WinMain<'a> { Preview::Iso(text) => { impl_preview!(text, tab, length, canvas, line_number_width, window) } + Preview::Socket(text) => { + impl_preview!(text, tab, length, canvas, line_number_width, window) + } + Preview::BlockDevice(text) => { + impl_preview!(text, tab, length, canvas, line_number_width, window) + } + Preview::FifoCharDevice(text) => { + impl_preview!(text, tab, length, canvas, line_number_width, window) + } Preview::Empty => (), } @@ -440,7 +527,7 @@ impl<'a> Draw for WinSecondary<'a> { Mode::Navigate(mode) => self.navigate(mode, canvas), Mode::NeedConfirmation(mode) => self.confirm(self.status, self.tab, mode, canvas), Mode::InputCompleted(_) => self.completion(self.tab, canvas), - Mode::InputSimple(mode) => Self::input_simple(mode, canvas), + Mode::InputSimple(mode) => Self::display_static_lines(mode.lines(), canvas), _ => Ok(()), }?; self.cursor(self.tab, canvas)?; @@ -463,7 +550,7 @@ impl<'a> WinSecondary<'a> { } fn first_line(&self, tab: &Tab, canvas: &mut dyn Canvas) -> Result<()> { - draw_colored_strings(0, 0, self.create_first_row(tab)?, canvas) + draw_colored_strings(0, 0, self.create_first_row(tab)?, canvas, false) } fn create_first_row(&self, tab: &Tab) -> Result<Vec<String>> { @@ -482,7 +569,7 @@ impl<'a> WinSecondary<'a> { vec![format!("{password_kind}"), tab.input.password()] } Mode::InputCompleted(mode) => { - let mut completion_strings = vec![format!("{}", &tab.mode), tab.input.string()]; + let mut completion_strings = vec![tab.mode.to_string(), tab.input.string()]; if let Some(completion) = tab.completion.complete_input_string(&tab.input.string()) { completion_strings.push(completion.to_owned()) @@ -496,10 +583,7 @@ impl<'a> WinSecondary<'a> { completion_strings } _ => { - vec![ - format!("{}", tab.mode.clone()), - format!("{}", tab.input.string()), - ] + vec![tab.mode.to_string(), tab.input.string()] } }; Ok(first_row) @@ -519,25 +603,6 @@ impl<'a> WinSecondary<'a> { Ok(()) } - fn input_simple_lines(mode: InputSimple) -> &'static [&'static str] { - match mode { - InputSimple::Chmod => &CHMOD_LINES, - InputSimple::Filter => &FILTER_LINES, - InputSimple::Newdir => &NEWDIR_LINES, - InputSimple::Newfile => &NEWFILE_LINES, - InputSimple::Password(_, _, _) => &PASSWORD_LINES, - InputSimple::RegexMatch => ®EX_LINES, - InputSimple::Rename => &RENAME_LINES, - InputSimple::SetNvimAddr => &NVIM_ADDRESS_LINES, - InputSimple::Shell => &SHELL_LINES, - InputSimple::Sort => &SORT_LINES, - } - } - - fn input_simple(mode: InputSimple, canvas: &mut dyn Canvas) -> Result<()> { - Self::display_static_lines(Self::input_simple_lines(mode), canvas) - } - fn display_static_lines(lines: &[&str], canvas: &mut dyn Canvas) -> Result<()> { for (row, line, attr) in enumerated_colored_iter!(lines) { canvas.print_with_attr(row + ContentWindow::WINDOW_MARGIN_TOP, 4, line, *attr)?; @@ -548,11 +613,7 @@ impl<'a> WinSecondary<'a> { /// Display a cursor in the top row, at a correct column. fn cursor(&self, tab: &Tab, canvas: &mut dyn Canvas) -> Result<()> { match tab.mode { - Mode::Normal - | Mode::Tree - | Mode::Navigate(Navigate::Marks(_)) - | Mode::Navigate(_) - | Mode::Preview => { + Mode::Normal | Mode::Tree | Mode::Navigate(_) | Mode::Preview => { canvas.show_cursor(false)?; } Mode::InputSimple(InputSimple::Sort) => { @@ -641,17 +702,26 @@ impl<'a> WinSecondary<'a> { "Enter: restore the selected file - x: delete permanently", )?; let content = &selectable.content(); - for (row, trashinfo, attr) in enumerated_colored_iter!(content) { - let mut attr = *attr; - if row == selectable.index() { - attr.effect |= Effect::REVERSE; - } + if content.is_empty() { let _ = canvas.print_with_attr( - row + ContentWindow::WINDOW_MARGIN_TOP, + ContentWindow::WINDOW_MARGIN_TOP + 2, 4, - &format!("{trashinfo}"), - attr, + "Trash is empty", + ATTR_YELLOW_BOLD, ); + } else { + for (row, trashinfo, attr) in enumerated_colored_iter!(content) { + let mut attr = *attr; + if row == selectable.index() { + attr.effect |= Effect::REVERSE; + } + let _ = canvas.print_with_attr( + row + ContentWindow::WINDOW_MARGIN_TOP, + 4, + &format!("{trashinfo}"), + attr, + ); + } } Ok(()) } @@ -778,7 +848,7 @@ impl<'a> WinSecondary<'a> { )?; } } - NeedConfirmation::Copy | NeedConfirmation::Delete | NeedConfirmation::Move => { + _ => { for (row, path) in status.flagged.content.iter().enumerate() { canvas.print_with_attr( row + ContentWindow::WINDOW_MARGIN_TOP + 2, @@ -789,20 +859,12 @@ impl<'a> WinSecondary<'a> { } } } - let confirmation_string = match confirmed_mode { - NeedConfirmation::Copy => { - format!( - "Files will be copied to {}", - tab.path_content.path_to_str()? - ) - } - NeedConfirmation::Delete => "Files will deleted permanently".to_owned(), - NeedConfirmation::Move => { - format!("Files will be moved to {}", tab.path_content.path_to_str()?) - } - NeedConfirmation::EmptyTrash => "Trash will be emptied".to_owned(), - }; - canvas.print_with_attr(2, 3, &confirmation_string, ATTR_YELLOW_BOLD)?; + canvas.print_with_attr( + 2, + 3, + &confirmed_mode.confirmation_string(&tab.path_content.path_to_str()), + ATTR_YELLOW_BOLD, + )?; Ok(()) } @@ -927,8 +989,21 @@ impl Display { colors: &Colors, ) -> Result<()> { let (width, _) = self.term.term_size()?; - let win_main_left = WinMain::new(status, 0, disk_space_tab_0, colors, 0, false); - let win_main_right = WinMain::new(status, 1, disk_space_tab_1, colors, width / 2, true); + let (first_selected, second_selected) = (status.index == 0, status.index == 1); + let attributes_left = WinMainAttributes::new( + 0, + false, + first_selected, + status.tabs[0].need_second_window(), + ); + let win_main_left = WinMain::new(status, 0, disk_space_tab_0, colors, attributes_left); + let attributes_right = WinMainAttributes::new( + width / 2, + true, + second_selected, + status.tabs[1].need_second_window(), + ); + let win_main_right = WinMain::new(status, 1, disk_space_tab_1, colors, attributes_right); let win_second_left = WinSecondary::new(status, 0); let win_second_right = WinSecondary::new(status, 1); let (border_left, border_right) = self.borders(status); @@ -956,7 +1031,9 @@ impl Display { disk_space_tab_0: &str, colors: &Colors, ) -> Result<()> { - let win_main_left = WinMain::new(status, 0, disk_space_tab_0, colors, 0, false); + let attributes_left = + WinMainAttributes::new(0, false, true, status.tabs[0].need_second_window()); + let win_main_left = WinMain::new(status, 0, disk_space_tab_0, colors, attributes_left); let win_second_left = WinSecondary::new(status, 0); let percent_left = self.size_for_second_window(&status.tabs[0])?; let win = self.vertical_split( @@ -986,15 +1063,21 @@ const fn color_to_attr(color: Color) -> Attr { effect: Effect::empty(), } } + fn draw_colored_strings( row: usize, offset: usize, strings: Vec<String>, canvas: &mut dyn Canvas, + reverse: bool, ) -> Result<()> { let mut col = 0; for (text, attr) in std::iter::zip(strings.iter(), FIRST_LINE_COLORS.iter().cycle()) { - col += canvas.print_with_attr(row, offset + col, text, *attr)?; + let mut attr = *attr; + if reverse { + attr.effect |= Effect::REVERSE; + } + col += canvas.print_with_attr(row, offset + col, text, attr)?; } Ok(()) } |