diff options
-rw-r--r-- | Cargo.lock | 2 | ||||
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | development.md | 11 | ||||
-rw-r--r-- | src/event_dispatch.rs | 25 | ||||
-rw-r--r-- | src/event_exec.rs | 88 | ||||
-rw-r--r-- | src/tab.rs | 4 | ||||
-rw-r--r-- | src/term_manager.rs | 524 | ||||
-rw-r--r-- | src/tree.rs | 25 |
8 files changed, 417 insertions, 264 deletions
@@ -935,7 +935,7 @@ dependencies = [ [[package]] name = "fm-tui" -version = "0.1.10" +version = "0.1.11" dependencies = [ "chrono", "clap 4.0.32", @@ -1,6 +1,6 @@ [package] name = "fm-tui" -version = "0.1.10" +version = "0.1.11" authors = ["Quentin Konieczko <qu3nt1n@gmail.com>"] edition = "2021" license-file = "LICENSE.txt" diff --git a/development.md b/development.md index 0953279..3ca3f7c 100644 --- a/development.md +++ b/development.md @@ -323,7 +323,16 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally. - [ ] vim keys, harmonize keybinds with ranger - [ ] zoxide support -- [ ] Version 0.1.11 : window for mode displaying info (completion, marks, shortcut, jump) +- Version 0.1.11 : + + - [x] window for mode displaying info (completion, marks, shortcut, jump) + - [x] clicks on right pane should select it first + - [x] doucle click left = right click : open file + - [x] wheel select pane + - [x] enable mouse in tree mode + - [x] wheel -> next/prev sibling (somewhat okay) + - [x] left/right + - [x] FIX: quit from preview is weird - [ ] Version 0.1.12 : scrollable shortcuts, marks & history diff --git a/src/event_dispatch.rs b/src/event_dispatch.rs index a7ad1a0..ed48851 100644 --- a/src/event_dispatch.rs +++ b/src/event_dispatch.rs @@ -27,13 +27,23 @@ impl EventDispatcher { /// which needs to know those keybindings. pub fn dispatch(&self, status: &mut Status, ev: Event) -> FmResult<()> { match ev { - Event::Key(Key::WheelUp(_, _, _)) => EventExec::event_move_up(status), - Event::Key(Key::WheelDown(_, _, _)) => EventExec::event_move_down(status), - Event::Key(Key::SingleClick(MouseButton::Left, row, _)) => { + Event::Key(Key::WheelUp(_, col, _)) => { + EventExec::event_select_pane(status, col)?; + EventExec::event_move_up(status) + } + Event::Key(Key::WheelDown(_, col, _)) => { + EventExec::event_select_pane(status, col)?; + EventExec::event_move_down(status) + } + Event::Key(Key::SingleClick(MouseButton::Left, row, col)) => { + EventExec::event_select_pane(status, col)?; EventExec::event_select_row(status, row) } - Event::Key(Key::SingleClick(MouseButton::Right, row, _)) => { - EventExec::event_right_click(status, row) + Event::Key(Key::SingleClick(MouseButton::Right, row, col)) + | Event::Key(Key::DoubleClick(MouseButton::Left, row, col)) => { + EventExec::event_select_pane(status, col)?; + EventExec::event_select_row(status, row)?; + EventExec::event_right_click(status) } Event::User(_) => EventExec::refresh_status(status), Event::Resize { width, height } => EventExec::resize(status, width, height), @@ -86,7 +96,10 @@ impl EventDispatcher { Mode::Navigate(Navigate::Trash) if c == 'x' => { EventExec::event_trash_remove_file(status) } - Mode::Preview | Mode::Navigate(_) => EventExec::event_normal(status.selected()), + Mode::Preview | Mode::Navigate(_) => { + status.selected().set_mode(Mode::Normal); + EventExec::event_normal(status.selected()) + } }, _ => Ok(()), } diff --git a/src/event_exec.rs b/src/event_exec.rs index 6df3da9..1e78529 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -428,13 +428,36 @@ impl EventExec { tab.input.cursor_end() } + /// Select the left or right tab depending on where the user clicked. + pub fn event_select_pane(status: &mut Status, col: u16) -> FmResult<()> { + let (width, _) = status.term_size()?; + if (col as usize) < width / 2 { + status.select_tab(0)?; + } else { + status.select_tab(1)?; + }; + Ok(()) + } + /// Select a given row, if there's something in it. pub fn event_select_row(status: &mut Status, row: u16) -> FmResult<()> { - if let Mode::Normal = status.selected_non_mut().mode { - let tab = status.selected(); - let index = Self::row_to_index(row); - tab.path_content.select_index(index); - tab.window.scroll_to(index); + let colors = &status.config_colors.clone(); + let tab = status.selected(); + match tab.mode { + Mode::Normal => { + let index = Self::row_to_index(row); + tab.path_content.select_index(index); + tab.window.scroll_to(index); + } + Mode::Tree => { + let index = Self::row_to_index(row) + 1; + tab.directory.tree.unselect_children(); + tab.directory.tree.position = tab.directory.tree.position_from_index(index); + let (_, _, node) = tab.directory.tree.select_from_position()?; + tab.directory.make_preview(colors); + tab.directory.tree.current_node = node; + } + _ => (), } Ok(()) } @@ -641,7 +664,8 @@ impl EventExec { Ok(()) } - /// Reset the mode to normal. + /// Leave a mode requiring a confirmation without doing anything. + /// Reset the mode to the previous mode. pub fn event_leave_need_confirmation(tab: &mut Tab) { tab.reset_mode(); } @@ -765,29 +789,11 @@ impl EventExec { } /// A right click opens a file or a directory. - pub fn event_right_click(status: &mut Status, row: u16) -> FmResult<()> { - if let Mode::Normal = status.selected_non_mut().mode { - let tab = status.selected(); - if tab.path_content.content.is_empty() - || row as usize > tab.path_content.content.len() + 1 - { - return Err(FmError::custom("event right click", "not found")); - } - let index = Self::row_to_index(row); - tab.path_content.select_index(index); - tab.window.scroll_to(index); - if let FileKind::Directory = tab - .path_content - .selected() - .ok_or_else(|| FmError::custom("event right click", "not found"))? - .file_kind - { - Self::exec_file(status) - } else { - Self::event_open_file(status) - } - } else { - Ok(()) + pub fn event_right_click(status: &mut Status) -> FmResult<()> { + match status.selected().mode { + Mode::Normal => Self::exec_file(status), + Mode::Tree => Self::exec_tree(status), + _ => Ok(()), } } @@ -1254,7 +1260,7 @@ impl EventExec { } Mode::InputCompleted(InputCompleted::Goto) => EventExec::exec_goto(status.selected())?, Mode::Normal => EventExec::exec_file(status)?, - Mode::Tree => EventExec::exec_tree(status.selected())?, + Mode::Tree => EventExec::exec_tree(status)?, Mode::NeedConfirmation(_) | Mode::Preview | Mode::InputCompleted(InputCompleted::Nothing) @@ -1513,24 +1519,16 @@ impl EventExec { } /// Execute the selected node if it's a file else enter the directory. - pub fn exec_tree(tab: &mut Tab) -> FmResult<()> { + pub fn exec_tree(status: &mut Status) -> FmResult<()> { + let colors = &status.config_colors.clone(); + let tab = status.selected(); let node = tab.directory.tree.current_node.clone(); if !node.fileinfo.path.is_dir() { - let filename = node.filename(); - let parent = node - .filepath() - .parent() - .ok_or_else(|| FmError::custom("exec_tree", "path should have a parent"))? - .to_owned(); - tab.set_pathcontent(&parent)?; - tab.set_mode(Mode::Normal); - tab.refresh_view()?; - tab.search_from(&filename, 0); - Ok(()) + Self::event_open_file(status) } else { - tab.set_pathcontent(&node.filepath())?; - tab.set_mode(Mode::Normal); - tab.refresh_view() + tab.set_pathcontent(&node.fileinfo.path)?; + tab.make_tree(colors)?; + Ok(()) } } } @@ -346,4 +346,8 @@ impl Tab { self.mode = self.previous_mode; self.previous_mode = Mode::Normal; } + + pub fn need_second_window(&self) -> bool { + !matches!(self.mode, Mode::Normal | Mode::Tree | Mode::Preview) + } } diff --git a/src/term_manager.rs b/src/term_manager.rs index 918554f..25637e4 100644 --- a/src/term_manager.rs +++ b/src/term_manager.rs @@ -25,6 +25,21 @@ use crate::trash::TrashInfo; /// At least 100 chars width to display 2 tabs. pub const MIN_WIDTH_FOR_DUAL_PANE: usize = 100; +const FIRST_LINE_COLORS: [Attr; 6] = [ + color_to_attr(Color::Rgb(231, 162, 156)), + color_to_attr(Color::Rgb(144, 172, 186)), + color_to_attr(Color::Rgb(214, 125, 83)), + color_to_attr(Color::Rgb(91, 152, 119)), + color_to_attr(Color::Rgb(152, 87, 137)), + color_to_attr(Color::Rgb(230, 189, 87)), +]; + +const ATTR_YELLOW_BOLD: Attr = Attr { + fg: Color::YELLOW, + bg: Color::Default, + effect: Effect::BOLD, +}; + /// Simple struct to read the events. pub struct EventReader { term: Arc<Term>, @@ -46,35 +61,23 @@ impl EventReader { macro_rules! impl_preview { ($text:ident, $tab:ident, $length:ident, $canvas:ident, $line_number_width:ident) => { for (i, line) in (*$text).window($tab.window.top, $tab.window.bottom, $length) { - let row = Self::calc_line_row(i, $tab); + let row = calc_line_row(i, $tab); $canvas.print(row, $line_number_width + 3, line)?; } }; } -struct WinTab<'a> { +struct WinMain<'a> { status: &'a Status, tab: &'a Tab, disk_space: &'a str, } -impl<'a> Draw for WinTab<'a> { +impl<'a> Draw for WinMain<'a> { fn draw(&self, canvas: &mut dyn Canvas) -> DrawResult<()> { + canvas.clear()?; match self.tab.mode { - Mode::Navigate(Navigate::Jump) => self.destination(canvas, &self.status.flagged), - Mode::Navigate(Navigate::History) => self.destination(canvas, &self.tab.history), - Mode::Navigate(Navigate::Shortcut) => self.destination(canvas, &self.tab.shortcut), - Mode::Navigate(Navigate::Trash) => self.trash(canvas, &self.status.trash), - Mode::NeedConfirmation(confirmed_mode) => { - self.confirmation(self.status, self.tab, confirmed_mode, canvas) - } - Mode::InputCompleted(_) => self.completion(self.tab, canvas), Mode::Preview => self.preview(self.tab, canvas), - Mode::InputSimple(InputSimple::Marks(_)) => self.marks(self.status, self.tab, canvas), - Mode::InputSimple(InputSimple::Rename) => match self.tab.previous_mode { - Mode::Tree => self.tree(self.status, self.tab, canvas), - _ => self.files(self.status, self.tab, canvas), - }, Mode::Tree => self.tree(self.status, self.tab, canvas), Mode::Normal => self.files(self.status, self.tab, canvas), _ => match self.tab.previous_mode { @@ -82,39 +85,31 @@ impl<'a> Draw for WinTab<'a> { _ => self.files(self.status, self.tab, canvas), }, }?; - self.cursor(self.tab, canvas)?; self.first_line(self.tab, self.disk_space, canvas)?; Ok(()) } } -impl<'a> Widget for WinTab<'a> {} +impl<'a> Widget for WinMain<'a> {} -impl<'a> WinTab<'a> { - const EDIT_BOX_OFFSET: usize = 9; - const SORT_CURSOR_OFFSET: usize = 37; +impl<'a> WinMain<'a> { const ATTR_LINE_NR: Attr = color_to_attr(Color::CYAN); - const ATTR_YELLOW: Attr = color_to_attr(Color::YELLOW); - const ATTR_YELLOW_BOLD: Attr = Attr { - fg: Color::YELLOW, - bg: Color::Default, - effect: Effect::BOLD, - }; - const FIRST_LINE_COLORS: [Attr; 6] = [ - color_to_attr(Color::Rgb(231, 162, 156)), - color_to_attr(Color::Rgb(144, 172, 186)), - color_to_attr(Color::Rgb(214, 125, 83)), - color_to_attr(Color::Rgb(91, 152, 119)), - color_to_attr(Color::Rgb(152, 87, 137)), - color_to_attr(Color::Rgb(230, 189, 87)), - ]; + + fn new(status: &'a Status, index: usize, disk_space: &'a str) -> Self { + Self { + status, + tab: &status.tabs[index], + disk_space, + } + } + /// Display the top line on terminal. /// Its content depends on the mode. /// In normal mode we display the path and number of files. /// When a confirmation is needed we ask the user to input `'y'` or /// something else. fn first_line(&self, tab: &Tab, disk_space: &str, canvas: &mut dyn Canvas) -> FmResult<()> { - self.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) } fn second_line(&self, status: &Status, tab: &Tab, canvas: &mut dyn Canvas) -> FmResult<()> { @@ -134,7 +129,7 @@ impl<'a> WinTab<'a> { } } Mode::InputSimple(InputSimple::Filter) => { - canvas.print_with_attr(1, 0, FILTER_PRESENTATION, Self::ATTR_YELLOW_BOLD)?; + canvas.print_with_attr(1, 0, FILTER_PRESENTATION, ATTR_YELLOW_BOLD)?; } _ => (), } @@ -160,7 +155,7 @@ impl<'a> WinTab<'a> { 1, 0, &format!("{}", &status.selected_non_mut().filter), - Self::ATTR_YELLOW_BOLD, + ATTR_YELLOW_BOLD, )?) } @@ -175,9 +170,6 @@ impl<'a> WinTab<'a> { format!("{} ", &tab.path_content.git_string()?), ] } - Mode::NeedConfirmation(confirmed_action) => { - vec![format!("{} (y/n)", confirmed_action)] - } Mode::Preview => match &tab.preview { Preview::Text(text_content) => { if matches!(text_content.kind, TextKind::HELP) { @@ -191,18 +183,18 @@ impl<'a> WinTab<'a> { } _ => Self::default_preview_first_line(tab), }, - Mode::InputSimple(InputSimple::Marks(MarkAction::Jump)) => { - vec!["Jump to...".to_owned()] - } - Mode::InputSimple(InputSimple::Marks(MarkAction::New)) => { - vec!["Save mark...".to_owned()] - } - _ => { - vec![ - format!("{}", tab.mode.clone()), - format!("{}", tab.input.string()), - ] - } + _ => match tab.previous_mode { + Mode::Normal | Mode::Tree => { + vec![ + format!("{} ", tab.path_content.path.display()), + format!("{} files ", tab.path_content.true_len()), + format!("{} ", tab.path_content.used_space()), + format!("Avail: {} ", disk_space), + format!("{} ", &tab.path_content.git_string()?), + ] + } + _ => vec![], + }, }; Ok(first_row) } @@ -223,20 +215,6 @@ impl<'a> WinTab<'a> { } } - fn draw_colored_strings( - &self, - row: usize, - offset: usize, - strings: Vec<String>, - canvas: &mut dyn Canvas, - ) -> FmResult<()> { - let mut col = 0; - for (text, attr) in std::iter::zip(strings.iter(), Self::FIRST_LINE_COLORS.iter().cycle()) { - col += canvas.print_with_attr(row, offset + col, text, *attr)?; - } - Ok(()) - } - /// Displays the current directory content, one line per item like in /// `ls -l`. /// @@ -283,6 +261,174 @@ impl<'a> WinTab<'a> { Ok(()) } + fn print_line_number( + row_position_in_canvas: usize, + line_number_to_print: usize, + canvas: &mut dyn Canvas, + ) -> FmResult<usize> { + Ok(canvas.print_with_attr( + row_position_in_canvas, + 0, + &line_number_to_print.to_string(), + Self::ATTR_LINE_NR, + )?) + } + + /// Display a scrollable preview of a file. + /// Multiple modes are supported : + /// if the filename extension is recognized, the preview is highlighted, + /// if the file content is recognized as binary, an hex dump is previewed with 16 bytes lines, + /// else the content is supposed to be text and shown as such. + /// It may fail to recognize some usual extensions, notably `.toml`. + /// It may fail to recognize small files (< 1024 bytes). + fn preview(&self, tab: &Tab, canvas: &mut dyn Canvas) -> FmResult<()> { + let length = tab.preview.len(); + let line_number_width = length.to_string().len(); + match &tab.preview { + Preview::Syntaxed(syntaxed) => { + for (i, vec_line) in (*syntaxed).window(tab.window.top, tab.window.bottom, length) { + let row_position = calc_line_row(i, tab); + Self::print_line_number(row_position, i + 1, canvas)?; + for token in vec_line.iter() { + token.print(canvas, row_position, line_number_width)?; + } + } + } + Preview::Binary(bin) => { + let line_number_width_hex = format!("{:x}", bin.len() * 16).len(); + + for (i, line) in (*bin).window(tab.window.top, tab.window.bottom, length) { + let row = calc_line_row(i, tab); + + canvas.print_with_attr( + row, + 0, + &format_line_nr_hex(i + 1 + tab.window.top, line_number_width_hex), + Self::ATTR_LINE_NR, + )?; + line.print(canvas, row, line_number_width_hex + 1); + } + } + Preview::Thumbnail(image) => { + let (width, height) = canvas.size()?; + + if let Ok(scaled_image) = (*image).resized_rgb8(width as u32 / 2, height as u32 - 3) + { + let (width, _) = scaled_image.dimensions(); + for (i, pixel) in scaled_image.pixels().enumerate() { + let (r, g, b) = pixel_values(pixel); + let (row, col) = pixel_position(i, width); + print_pixel(canvas, row, col, r, g, b)?; + } + } else { + canvas.print( + 3, + 3, + &format!("Not a displayable image: {:?}", image.img_path), + )?; + } + } + Preview::Directory(directory) => { + for (i, (prefix, colored_string)) in + (directory).window(tab.window.top, tab.window.bottom, length) + { + let row = calc_line_row(i, tab); + let col = canvas.print(row, line_number_width, prefix)?; + canvas.print_with_attr( + row, + line_number_width + col + 1, + &colored_string.text, + colored_string.attr, + )?; + } + } + Preview::Archive(text) => impl_preview!(text, tab, length, canvas, line_number_width), + Preview::Exif(text) => impl_preview!(text, tab, length, canvas, line_number_width), + Preview::Media(text) => impl_preview!(text, tab, length, canvas, line_number_width), + Preview::Pdf(text) => impl_preview!(text, tab, length, canvas, line_number_width), + Preview::Text(text) => impl_preview!(text, tab, length, canvas, line_number_width), + + Preview::Empty => (), + } + Ok(()) + } +} + +struct WinSecondary<'a> { + status: &'a Status, + tab: &'a Tab, +} +impl<'a> Draw for WinSecondary<'a> { + fn draw(&self, canvas: &mut dyn Canvas) -> DrawResult<()> { + canvas.clear()?; + match self.tab.mode { + Mode::Navigate(Navigate::Jump) => self.destination(canvas, &self.status.flagged), + Mode::Navigate(Navigate::History) => self.destination(canvas, &self.tab.history), + Mode::Navigate(Navigate::Shortcut) => self.destination(canvas, &self.tab.shortcut), + Mode::Navigate(Navigate::Trash) => self.trash(canvas, &self.status.trash), + Mode::NeedConfirmation(confirmed_mode) => { + self.confirmation(self.status, self.tab, confirmed_mode, canvas) + } + Mode::InputCompleted(_) => self.completion(self.tab, canvas), + Mode::InputSimple(InputSimple::Marks(_)) => self.marks(self.status, self.tab, canvas), + _ => Ok(()), + }?; + self.cursor(self.tab, canvas)?; + self.first_line(self.tab, canvas)?; + Ok(()) + } +} + +impl<'a> WinSecondary<'a> { + const EDIT_BOX_OFFSET: usize = 9; + const ATTR_YELLOW: Attr = color_to_attr(Color::YELLOW); + const SORT_CURSOR_OFFSET: usize = 37; + + fn new(status: &'a Status, index: usize) -> Self { + Self { + status, + tab: &status.tabs[index], + } + } + + fn first_line(&self, tab: &Tab, canvas: &mut dyn Canvas) -> FmResult<()> { + draw_colored_strings(0, 0, self.create_first_row(tab)?, canvas) + } + + fn create_first_row(&self, tab: &Tab) -> FmResult<Vec<String>> { + let first_row = match tab.mode { + Mode::NeedConfirmation(confirmed_action) => { + vec![format!("{} (y/n)", confirmed_action)] + } + Mode::InputSimple(InputSimple::Marks(MarkAction::Jump)) => { + vec!["Jump to...".to_owned()] + } + Mode::InputSimple(InputSimple::Marks(MarkAction::New)) => { + vec!["Save mark...".to_owned()] + } + _ => { + vec![ + format!("{}", tab.mode.clone()), + format!("{}", tab.input.string()), + ] + } + }; + Ok(first_row) + } + + /// Display the possible completion items. The currently selected one is + /// reversed. + fn completion(&self, tab: &Tab, canvas: &mut dyn Canvas) -> FmResult<()> { + canvas.set_cursor(0, tab.input.cursor_index + Self::EDIT_BOX_OFFSET)?; + for (row, candidate) in tab.completion.proposals.iter().enumerate() { + let mut attr = Attr::default(); + if row == tab.completion.index { + attr.effect |= Effect::REVERSE; + } + canvas.print_with_attr(row + ContentWindow::WINDOW_MARGIN_TOP, 4, candidate, attr)?; + } + Ok(()) + } /// Display a cursor in the top row, at a correct column. fn cursor(&self, tab: &Tab, canvas: &mut dyn Canvas) -> FmResult<()> { match tab.mode { @@ -294,6 +440,7 @@ impl<'a> WinTab<'a> { canvas.show_cursor(false)?; } Mode::InputSimple(InputSimple::Sort) => { + canvas.show_cursor(true)?; canvas.set_cursor(0, Self::SORT_CURSOR_OFFSET)?; } Mode::InputSimple(_) | Mode::InputCompleted(_) => { @@ -301,6 +448,7 @@ impl<'a> WinTab<'a> { canvas.set_cursor(0, tab.input.cursor_index + Self::EDIT_BOX_OFFSET)?; } Mode::NeedConfirmation(confirmed_action) => { + canvas.show_cursor(true)?; canvas.set_cursor(0, confirmed_action.cursor_offset())?; } } @@ -352,16 +500,12 @@ impl<'a> WinTab<'a> { Ok(()) } - /// Display the possible completion items. The currently selected one is - /// reversed. - fn completion(&self, tab: &Tab, canvas: &mut dyn Canvas) -> FmResult<()> { - canvas.set_cursor(0, tab.input.cursor_index + Self::EDIT_BOX_OFFSET)?; - for (row, candidate) in tab.completion.proposals.iter().enumerate() { - let mut attr = Attr::default(); - if row == tab.completion.index { - attr.effect |= Effect::REVERSE; - } - canvas.print_with_attr(row + ContentWindow::WINDOW_MARGIN_TOP, 4, candidate, attr)?; + fn marks(&self, status: &Status, tab: &Tab, canvas: &mut dyn Canvas) -> FmResult<()> { + canvas.print_with_attr(2, 1, "mark path", Self::ATTR_YELLOW)?; + + for (i, line) in status.marks.as_strings().iter().enumerate() { + let row = calc_line_row(i, tab) + 2; + canvas.print(row, 3, line)?; } Ok(()) } @@ -411,118 +555,14 @@ impl<'a> WinTab<'a> { } NeedConfirmation::EmptyTrash => "Trash will be emptied".to_owned(), }; - canvas.print_with_attr(2, 3, &confirmation_string, Self::ATTR_YELLOW_BOLD)?; + canvas.print_with_attr(2, 3, &confirmation_string, ATTR_YELLOW_BOLD)?; Ok(()) } - - fn print_line_number( - row_position_in_canvas: usize, - line_number_to_print: usize, - canvas: &mut dyn Canvas, - ) -> FmResult<usize> { - Ok(canvas.print_with_attr( - row_position_in_canvas, - 0, - &line_number_to_print.to_string(), - Self::ATTR_LINE_NR, - )?) - } - - /// Display a scrollable preview of a file. - /// Multiple modes are supported : - /// if the filename extension is recognized, the preview is highlighted, - /// if the file content is recognized as binary, an hex dump is previewed with 16 bytes lines, - /// else the content is supposed to be text and shown as such. - /// It may fail to recognize some usual extensions, notably `.toml`. - /// It may fail to recognize small files (< 1024 bytes). - fn preview(&self, tab: &Tab, canvas: &mut dyn Canvas) -> FmResult<()> { - let length = tab.preview.len(); - let line_number_width = length.to_string().len(); - match &tab.preview { - Preview::Syntaxed(syntaxed) => { - for (i, vec_line) in (*syntaxed).window(tab.window.top, tab.window.bottom, length) { - let row_position = Self::calc_line_row(i, tab); - Self::print_line_number(row_position, i + 1, canvas)?; - for token in vec_line.iter() { - token.print(canvas, row_position, line_number_width)?; - } - } - } - Preview::Binary(bin) => { - let line_number_width_hex = format!("{:x}", bin.len() * 16).len(); - - for (i, line) in (*bin).window(tab.window.top, tab.window.bottom, length) { - let row = Self::calc_line_row(i, tab); - - canvas.print_with_attr( - row, - 0, - &format_line_nr_hex(i + 1 + tab.window.top, line_number_width_hex), - Self::ATTR_LINE_NR, - )?; - line.print(canvas, row, line_number_width_hex + 1); - } - } - Preview::Thumbnail(image) => { - let (width, height) = canvas.size()?; - - if let Ok(scaled_image) = (*image).resized_rgb8(width as u32 / 2, height as u32 - 3) - { - let (width, _) = scaled_image.dimensions(); - for (i, pixel) in scaled_image.pixels().enumerate() { - let (r, g, b) = pixel_values(pixel); - let (row, col) = pixel_position(i, width); - print_pixel(canvas, row, col, r, g, b)?; - } - } else { - canvas.print( - 3, - 3, - &format!("Not a displayable image: {:?}", image.img_path), - )?; - } - } - Preview::Directory(directory) => { - for (i, (prefix, colored_string)) in - (directory).window(tab.window.top, tab.window.bottom, length) - { - let row = Self::calc_line_row(i, tab); - let col = canvas.print(row, line_number_width, prefix)?; - canvas.print_with_attr( - row, - line_number_width + col + 1, - &colored_string.text, - colored_string.attr, - )?; - } - } - Preview::Archive(text) => impl_preview!(text, tab, length, canvas, line_number_width), - Preview::Exif(text) => impl_preview!(text, tab, length, canvas, line_number_width), - Preview::Media(text) => impl_preview!(text, tab, length, canvas, line_number_width), - Preview::Pdf(text) => impl_preview!(text, tab, length, canvas, line_number_width), - Preview::Text(text) => impl_preview!(text, tab, length, canvas, line_number_width), - - Preview::Empty => (), - } - Ok(()) - } - - fn marks(&self, status: &Status, tab: &Tab, canvas: &mut dyn Canvas) -> FmResult<()> { - canvas.print_with_attr(2, 1, "mark path", Self::ATTR_YELLOW)?; - - for (i, line) in status.marks.as_strings().iter().enumerate() { - let row = Self::calc_line_row(i, tab) + 2; - canvas.print(row, 3, line)?; - } - Ok(()) - } - - fn calc_line_row(i: usize, tab: &Tab) -> usize { - i + ContentWindow::WINDOW_MARGIN_TOP - tab.window.top - } } +impl<'a> Widget for WinSecondary<'a> {} + /// Is responsible for displaying content in the terminal. /// It uses an already created terminal. pub struct Display { @@ -547,6 +587,11 @@ impl Display { Ok(self.term.show_cursor(true)?) } + fn hide_cursor(&self) -> FmResult<()> { + self.term.set_cursor(0, 0)?; + Ok(self.term.show_cursor(false)?) + } + /// Display every possible content in the terminal. /// /// The top line @@ -565,6 +610,7 @@ impl Display { /// Displays one pane or two panes, depending of the width and current /// status of the application. pub fn display_all(&mut self, status: &Status) -> FmResult<()> { + self.hide_cursor()?; self.term.clear()?; let (width, _) = self.term.term_size()?; @@ -578,42 +624,85 @@ impl Display { Ok(self.term.present()?) } + fn size_for_second_window(&self, tab: &Tab) -> FmResult<usize> { + if tab.need_second_window() { + Ok(self.height()? / 2) + } else { + Ok(0) + } + } + + fn vertical_split<'a>( + &self, + win_main: &'a WinMain, + win_secondary: &'a WinSecondary, + border: Attr, + size: usize, + ) -> FmResult<VSplit<'a>> { + Ok(VSplit::default() + .split( + Win::new(win_main) + .basis(self.height()? - size) + .shrink(4) + .border(true) + .border_attr(border), + ) + .split( + Win::new(win_secondary) + .basis(size) + .shrink(0) + .border(true) + .border_attr(border), + )) + } + + fn borders(&self, status: &Status) -> (Attr, Attr) { + if status.index == 0 { + (Self::SELECTED_BORDER, Self::INERT_BORDER) + } else { + (Self::INERT_BORDER, Self::SELECTED_BORDER) + } + } + fn draw_dual_pane( &m |