summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock2
-rw-r--r--Cargo.toml2
-rw-r--r--development.md11
-rw-r--r--src/event_dispatch.rs25
-rw-r--r--src/event_exec.rs88
-rw-r--r--src/tab.rs4
-rw-r--r--src/term_manager.rs524
-rw-r--r--src/tree.rs25
8 files changed, 417 insertions, 264 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 53d06c60..f51a037f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -935,7 +935,7 @@ dependencies = [
[[package]]
name = "fm-tui"
-version = "0.1.10"
+version = "0.1.11"
dependencies = [
"chrono",
"clap 4.0.32",
diff --git a/Cargo.toml b/Cargo.toml
index 3fb0ee9f..9ea1e28e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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 0953279c..3ca3f7c6 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 a7ad1a02..ed488519 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 6df3da95..1e78529f 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(())
}
}
}
diff --git a/src/tab.rs b/src/tab.rs
index bf7c2ffa..573b23b9 100644
--- a/src/tab.rs
+++ b/src/tab.rs
@@ -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 918554f2..25637e45 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() {
+