diff options
-rw-r--r-- | src/actioner.rs | 211 | ||||
-rw-r--r-- | src/completion.rs | 1 | ||||
-rw-r--r-- | src/content_window.rs | 2 | ||||
-rw-r--r-- | src/display.rs | 35 | ||||
-rw-r--r-- | src/input.rs | 1 | ||||
-rw-r--r-- | src/last_edition.rs | 2 | ||||
-rw-r--r-- | src/lib.rs | 1 | ||||
-rw-r--r-- | src/main.rs | 12 | ||||
-rw-r--r-- | src/preview.rs | 5 | ||||
-rw-r--r-- | src/status.rs | 1 | ||||
-rw-r--r-- | src/tabs.rs | 47 | ||||
-rw-r--r-- | src/visited.rs | 2 |
12 files changed, 191 insertions, 129 deletions
diff --git a/src/actioner.rs b/src/actioner.rs index 5db9dd3..4edec7d 100644 --- a/src/actioner.rs +++ b/src/actioner.rs @@ -4,11 +4,11 @@ use tuikit::prelude::{Event, Key, MouseButton}; use crate::config::Keybindings; use crate::event_char::EventChar; use crate::mode::Mode; -use crate::status::Status; +use crate::tabs::Tabs; -/// Struct which mutates `Status`. +/// Struct which mutates `tabs.selected().. /// Holds a mapping which can't be static since it's read from a config file. -/// All keys are mapped to relevent events on status. +/// All keys are mapped to relevent events on tabs.selected(). /// Keybindings are read from `Config`. pub struct Actioner { binds: HashMap<char, EventChar>, @@ -16,7 +16,7 @@ pub struct Actioner { impl Actioner { /// Creates a map of configurable keybindings to `EventChar` - /// The `EventChar` is then associated to a `Status` method. + /// The `EventChar` is then associated to a `tabs.selected(). method. pub fn new(keybindings: &Keybindings) -> Self { let binds = HashMap::from([ (keybindings.toggle_hidden, EventChar::ToggleHidden), @@ -50,224 +50,227 @@ impl Actioner { Self { binds } } /// Reaction to received events. - pub fn read_event(&self, status: &mut Status, ev: Event) { + pub fn read_event(&self, tabs: &mut Tabs, ev: Event) { match ev { - Event::Key(Key::ESC) => self.escape(status), - Event::Key(Key::Up) => self.up(status), - Event::Key(Key::Down) => self.down(status), - Event::Key(Key::Left) => self.left(status), - Event::Key(Key::Right) => self.right(status), - Event::Key(Key::Backspace) => self.backspace(status), - Event::Key(Key::Ctrl('d')) => self.delete(status), - Event::Key(Key::Ctrl('q')) => self.escape(status), - Event::Key(Key::Delete) => self.delete(status), - Event::Key(Key::Char(c)) => self.char(status, c), - Event::Key(Key::Home) => self.home(status), - Event::Key(Key::End) => self.end(status), - Event::Key(Key::PageDown) => self.page_down(status), - Event::Key(Key::PageUp) => self.page_up(status), - Event::Key(Key::Enter) => self.enter(status), - Event::Key(Key::Tab) => self.tab(status), - Event::Key(Key::WheelUp(_, _, _)) => self.up(status), - Event::Key(Key::WheelDown(_, _, _)) => self.down(status), - Event::Key(Key::SingleClick(MouseButton::Left, row, _)) => self.left_click(status, row), - Event::Key(Key::SingleClick(MouseButton::Right, row, _)) => { - self.right_click(status, row) - } + Event::Key(Key::ESC) => self.escape(tabs), + Event::Key(Key::Up) => self.up(tabs), + Event::Key(Key::Down) => self.down(tabs), + Event::Key(Key::Left) => self.left(tabs), + Event::Key(Key::Right) => self.right(tabs), + Event::Key(Key::Backspace) => self.backspace(tabs), + Event::Key(Key::Ctrl('d')) => self.delete(tabs), + Event::Key(Key::Ctrl('q')) => self.escape(tabs), + Event::Key(Key::Delete) => self.delete(tabs), + Event::Key(Key::Char(c)) => self.char(tabs, c), + Event::Key(Key::Home) => self.home(tabs), + Event::Key(Key::End) => self.end(tabs), + Event::Key(Key::PageDown) => self.page_down(tabs), + Event::Key(Key::PageUp) => self.page_up(tabs), + Event::Key(Key::Enter) => self.enter(tabs), + Event::Key(Key::Tab) => self.tab(tabs), + Event::Key(Key::WheelUp(_, _, _)) => self.up(tabs), + Event::Key(Key::WheelDown(_, _, _)) => self.down(tabs), + Event::Key(Key::SingleClick(MouseButton::Left, row, _)) => self.left_click(tabs, row), + Event::Key(Key::SingleClick(MouseButton::Right, row, _)) => self.right_click(tabs, row), _ => {} } } /// Leaving a mode reset the window - fn escape(&self, status: &mut Status) { - status.event_normal() + fn escape(&self, tabs: &mut Tabs) { + tabs.selected().event_normal() } /// Move one line up - fn up(&self, status: &mut Status) { - match status.mode { - Mode::Normal | Mode::Preview => status.event_up_one_row(), - Mode::Jump => status.event_jumplist_prev(), - Mode::History => status.event_history_prev(), - Mode::Shortcut => status.event_shortcut_prev(), + fn up(&self, tabs: &mut Tabs) { + match tabs.selected().mode { + Mode::Normal | Mode::Preview => tabs.selected().event_up_one_row(), + Mode::Jump => tabs.selected().event_jumplist_prev(), + Mode::History => tabs.selected().event_history_prev(), + Mode::Shortcut => tabs.selected().event_shortcut_prev(), Mode::Goto | Mode::Exec | Mode::Search => { - status.completion.prev(); + tabs.selected().completion.prev(); } _ => (), } } /// Move one line down - fn down(&self, status: &mut Status) { - match status.mode { - Mode::Normal | Mode::Preview => status.event_down_one_row(), - Mode::Jump => status.event_jumplist_next(), - Mode::History => status.event_history_next(), - Mode::Shortcut => status.event_shortcut_next(), + fn down(&self, tabs: &mut Tabs) { + match tabs.selected().mode { + Mode::Normal | Mode::Preview => tabs.selected().event_down_one_row(), + Mode::Jump => tabs.selected().event_jumplist_next(), + Mode::History => tabs.selected().event_history_next(), + Mode::Shortcut => tabs.selected().event_shortcut_next(), Mode::Goto | Mode::Exec | Mode::Search => { - status.completion.next(); + tabs.selected().completion.next(); } _ => (), } } /// Move left in a string, move to parent in normal mode - fn left(&self, status: &mut Status) { - match status.mode { - Mode::Normal => status.event_move_to_parent(), + fn left(&self, tabs: &mut Tabs) { + match tabs.selected().mode { + Mode::Normal => tabs.selected().event_move_to_parent(), Mode::Rename | Mode::Chmod | Mode::Newdir | Mode::Newfile | Mode::Exec | Mode::Search - | Mode::Goto => status.event_move_cursor_left(), + | Mode::Goto => tabs.selected().event_move_cursor_left(), _ => (), } } /// Move right in a string, move to children in normal mode. - fn right(&self, status: &mut Status) { - match status.mode { - Mode::Normal => status.event_go_to_child(), + fn right(&self, tabs: &mut Tabs) { + match tabs.selected().mode { + Mode::Normal => tabs.selected().event_go_to_child(), Mode::Rename | Mode::Chmod | Mode::Newdir | Mode::Newfile | Mode::Exec | Mode::Search - | Mode::Goto => status.event_move_cursor_right(), + | Mode::Goto => tabs.selected().event_move_cursor_right(), _ => (), } } /// Deletes a char in input string - fn backspace(&self, status: &mut Status) { - match status.mode { + fn backspace(&self, tabs: &mut Tabs) { + match tabs.selected().mode { Mode::Rename | Mode::Newdir | Mode::Chmod | Mode::Newfile | Mode::Exec | Mode::Search - | Mode::Goto => status.event_delete_char_left(), + | Mode::Goto => tabs.selected().event_delete_char_left(), Mode::Normal => (), _ => (), } } /// Deletes chars right of cursor in input string. - fn delete(&self, status: &mut Status) { - match status.mode { + fn delete(&self, tabs: &mut Tabs) { + match tabs.selected().mode { Mode::Rename | Mode::Newdir | Mode::Chmod | Mode::Newfile | Mode::Exec | Mode::Search - | Mode::Goto => status.event_delete_chars_right(), + | Mode::Goto => tabs.selected().event_delete_chars_right(), Mode::Normal => (), _ => (), } } /// Move to top or beggining of line. - fn home(&self, status: &mut Status) { - match status.mode { - Mode::Normal | Mode::Preview => status.event_go_top(), - _ => status.event_cursor_home(), + fn home(&self, tabs: &mut Tabs) { + match tabs.selected().mode { + Mode::Normal | Mode::Preview => tabs.selected().event_go_top(), + _ => tabs.selected().event_cursor_home(), } } /// Move to end or end of line. - fn end(&self, status: &mut Status) { - match status.mode { - Mode::Normal | Mode::Preview => status.event_go_bottom(), - _ => status.event_cursor_end(), + fn end(&self, tabs: &mut Tabs) { + match tabs.selected().mode { + Mode::Normal | Mode::Preview => tabs.selected().event_go_bottom(), + _ => tabs.selected().event_cursor_end(), } } /// Move down 10 rows - fn page_down(&self, status: &mut Status) { - match status.mode { - Mode::Normal | Mode::Preview => status.event_page_down(), + fn page_down(&self, tabs: &mut Tabs) { + match tabs.selected().mode { + Mode::Normal | Mode::Preview => tabs.selected().event_page_down(), _ => (), } } /// Move up 10 rows - fn page_up(&self, status: &mut Status) { - match status.mode { - Mode::Normal | Mode::Preview => status.event_page_up(), + fn page_up(&self, tabs: &mut Tabs) { + match tabs.selected().mode { + Mode::Normal | Mode::Preview => tabs.selected().event_page_up(), _ => (), } } /// Execute a command - fn enter(&self, status: &mut Status) { - match status.mode { - Mode::Rename => status.exec_rename(), - Mode::Newfile => status.exec_newfile(), - Mode::Newdir => status.exec_newdir(), - Mode::Chmod => status.exec_chmod(), - Mode::Exec => status.exec_exec(), - Mode::Search => status.exec_search(), - Mode::Goto => status.exec_goto(), - Mode::RegexMatch => status.exec_regex(), - Mode::Jump => status.exec_jump(), - Mode::History => status.exec_history(), - Mode::Shortcut => status.exec_shortcut(), + fn enter(&self, tabs: &mut Tabs) { + match tabs.selected().mode { + Mode::Rename => tabs.selected().exec_rename(), + Mode::Newfile => tabs.selected().exec_newfile(), + Mode::Newdir => tabs.selected().exec_newdir(), + Mode::Chmod => tabs.selected().exec_chmod(), + Mode::Exec => tabs.selected().exec_exec(), + Mode::Search => tabs.selected().exec_search(), + Mode::Goto => tabs.selected().exec_goto(), + Mode::RegexMatch => tabs.selected().exec_regex(), + Mode::Jump => tabs.selected().exec_jump(), + Mode::History => tabs.selected().exec_history(), + Mode::Shortcut => tabs.selected().exec_shortcut(), Mode::Normal | Mode::NeedConfirmation | Mode::Help | Mode::Sort | Mode::Preview => (), } - status.input.reset(); - status.mode = Mode::Normal; + tabs.selected().input.reset(); + tabs.selected().mode = Mode::Normal; } /// Select this file - fn left_click(&self, status: &mut Status, row: u16) { - if let Mode::Normal = status.mode { - status.event_select_row(row) + fn left_click(&self, tabs: &mut Tabs, row: u16) { + if let Mode::Normal = tabs.selected().mode { + tabs.selected().event_select_row(row) } } /// Open a directory or a file - fn right_click(&self, status: &mut Status, row: u16) { - if let Mode::Normal = status.mode { - status.event_right_click(row) + fn right_click(&self, tabs: &mut Tabs, row: u16) { + if let Mode::Normal = tabs.selected().mode { + tabs.selected().event_right_click(row) } } /// Select next completion and insert it - fn tab(&self, status: &mut Status) { - match status.mode { - Mode::Goto | Mode::Exec | Mode::Search => status.event_replace_input_with_completion(), + fn tab(&self, tabs: &mut Tabs) { + match tabs.selected().mode { + Mode::Goto | Mode::Exec | Mode::Search => { + tabs.selected().event_replace_input_with_completion() + } + Mode::Normal => tabs.next(), _ => (), } } /// Match read key to a relevent event, depending on keybindings. /// Keybindings are read from `Config`. - fn char(&self, status: &mut Status, c: char) { - match status.mode { + fn char(&self, tabs: &mut Tabs, c: char) { + match tabs.selected().mode { Mode::Newfile | Mode::Newdir | Mode::Chmod | Mode::Rename | Mode::RegexMatch => { - status.event_text_insertion(c) + tabs.selected().event_text_insertion(c) + } + Mode::Goto | Mode::Exec | Mode::Search => { + tabs.selected().event_text_insert_and_complete(c) } - Mode::Goto | Mode::Exec | Mode::Search => status.event_text_insert_and_complete(c), Mode::Normal => match self.binds.get(&c) { - Some(event_char) => event_char.match_char(status), + Some(event_char) => event_char.match_char(tabs.selected()), None => (), }, - Mode::Help | Mode::Preview | Mode::Shortcut => status.event_normal(), + Mode::Help | Mode::Preview | Mode::Shortcut => tabs.selected().event_normal(), Mode::Jump => (), Mode::History => (), Mode::NeedConfirmation => { if c == 'y' { - status.exec_last_edition() + tabs.selected().exec_last_edition() } - status.event_leave_need_confirmation() + tabs.selected().event_leave_need_confirmation() } - Mode::Sort => status.event_leave_sort(c), + Mode::Sort => tabs.selected().event_leave_sort(c), } } } diff --git a/src/completion.rs b/src/completion.rs index 2ca8c1b..1af4ac3 100644 --- a/src/completion.rs +++ b/src/completion.rs @@ -4,6 +4,7 @@ use crate::fileinfo::PathContent; /// Holds a `Vec<String>` of possible completions and an `usize` index /// showing where the user is in the vec. +#[derive(Clone)] pub struct Completion { pub proposals: Vec<String>, pub index: usize, diff --git a/src/content_window.rs b/src/content_window.rs index 3054040..484b51d 100644 --- a/src/content_window.rs +++ b/src/content_window.rs @@ -5,7 +5,7 @@ use std::cmp::{max, min}; /// and this struct is responsible for that. /// Scrolling is done with `scroll_to`, `scroll_up_one`, `scroll_down_one` /// methods. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct ContentWindow { /// The index of the first displayed file. pub top: usize, diff --git a/src/display.rs b/src/display.rs index fa91c60..d0dfda2 100644 --- a/src/display.rs +++ b/src/display.rs @@ -10,6 +10,7 @@ use crate::help::HELP_LINES; use crate::mode::Mode; use crate::preview::Preview; use crate::status::Status; +use crate::tabs::Tabs; /// Is responsible for displaying content in the terminal. /// It uses an already created terminal. @@ -49,16 +50,17 @@ impl Display { /// The completion list if any. /// /// The preview in preview mode. - pub fn display_all(&mut self, status: &Status) { - self.first_line(status); - self.files(status); - self.cursor(status); - self.help(status); - self.jump_list(status); - self.history(status); - self.completion(status); - self.preview(status); - self.shortcuts(status); + pub fn display_all(&mut self, tabs: &mut Tabs) { + let index = tabs.index; + self.first_line(tabs.selected(), index); + self.files(tabs.selected()); + self.cursor(tabs.selected()); + self.help(tabs.selected()); + self.jump_list(tabs.selected()); + self.history(tabs.selected()); + self.completion(tabs.selected(), index); + self.preview(tabs.selected(), index); + self.shortcuts(tabs.selected()); } /// Reads and returns the `tuikit::term::Term` height. @@ -72,11 +74,12 @@ impl Display { /// 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(&mut self, status: &Status) { + fn first_line(&mut self, status: &Status, index: usize) { let first_row: String = match status.mode { Mode::Normal => { format!( - "Path: {} -- {} files", + "Tab: {} - Path: {} -- {} files", + index, status.path_content.path.to_str().unwrap(), status.path_content.files.len(), ) @@ -206,11 +209,11 @@ impl Display { /// Display the possible completion items. The currently selected one is /// reversed. - fn completion(&mut self, status: &Status) { + fn completion(&mut self, status: &Status, index: usize) { match status.mode { Mode::Goto | Mode::Exec | Mode::Search => { let _ = self.term.clear(); - self.first_line(status); + self.first_line(status, index); let _ = self .term .set_cursor(0, status.input.cursor_index + Self::EDIT_BOX_OFFSET); @@ -233,10 +236,10 @@ impl Display { /// 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(&mut self, status: &Status) { + fn preview(&mut self, status: &Status, index: usize) { if let Mode::Preview = status.mode { let _ = self.term.clear(); - self.first_line(status); + self.first_line(status, index); let length = status.preview.len(); let line_number_width = length.to_string().len(); diff --git a/src/input.rs b/src/input.rs index 17896bc..d99e7cb 100644 --- a/src/input.rs +++ b/src/input.rs @@ -1,5 +1,6 @@ /// Holds a string typed by the user and the cursor position. /// Methods allow mutation of this string and movement of the cursor. +#[derive(Clone)] pub struct Input { pub string: String, pub cursor_index: usize, diff --git a/src/last_edition.rs b/src/last_edition.rs index 026bce0..bb77e3f 100644 --- a/src/last_edition.rs +++ b/src/last_edition.rs @@ -1,7 +1,7 @@ use std::fmt; /// Different kind of last edition command received. -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum LastEdition { /// No edition command Nothing, @@ -13,4 +13,5 @@ pub mod mode; pub mod preview; pub mod shortcut; pub mod status; +pub mod tabs; pub mod visited; diff --git a/src/main.rs b/src/main.rs index b48e4c0..2fc3258 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,7 @@ use fm::args::Args; use fm::config::load_config; use fm::config::Colors; use fm::display::Display; -use fm::status::Status; +use fm::tabs::Tabs; static CONFIG_PATH: &str = "~/.config/fm/config.yaml"; @@ -29,21 +29,21 @@ fn main() { let config = load_config(CONFIG_PATH); let actioner = Actioner::new(&config.keybindings); let mut display = init_display(config.colors.clone()); - let mut status = Status::new(Args::parse(), config, display.height()); + let mut tabs = Tabs::new(Args::parse(), config, display.height()); while let Ok(event) = display.term.poll_event() { let _ = display.term.clear(); let (_width, height) = display.term.term_size().unwrap(); - status.set_height(height); + tabs.selected().set_height(height); - actioner.read_event(&mut status, event); + actioner.read_event(&mut tabs, event); - display.display_all(&status); + display.display_all(&mut tabs); let _ = display.term.present(); - if status.must_quit() { + if tabs.selected().must_quit() { reset_cursor(&display); break; }; diff --git a/src/preview.rs b/src/preview.rs index b76fb3d..169850c 100644 --- a/src/preview.rs +++ b/src/preview.rs @@ -11,6 +11,7 @@ use tuikit::term::Term; use crate::fileinfo::PathContent; +#[derive(Clone)] pub enum Preview { Syntaxed(SyntaxedContent), Text(TextContent), @@ -105,6 +106,7 @@ impl TextContent { } } +#[derive(Clone)] pub struct SyntaxedContent { pub highlighted_content: Box<Vec<Vec<SyntaxedString>>>, length: usize, @@ -161,6 +163,7 @@ impl SyntaxedContent { } } +#[derive(Clone)] pub struct SyntaxedString { // row: usize, col: usize, @@ -188,6 +191,7 @@ impl SyntaxedString { } } +#[derive(Clone)] pub struct BinaryContent { pub path: PathBuf, length: u64, @@ -233,6 +237,7 @@ impl BinaryContent { /// Holds a `Vec` of "bytes" (`u8`). /// It's mostly used to implement a `print` method. +#[derive(Clone)] pub struct Line { line: Vec<u8>, } diff --git a/src/status.rs b/src/status.rs index 0ee00bd..08bcd6e 100644 --- a/src/status.rs +++ b/src/status.rs @@ -24,6 +24,7 @@ use crate::visited::History; /// Is responsible to execute commands depending on received events, mutating /// the status of the application. /// Every change on the application comes here. +#[derive(Clone)] pub struct Status { /// The mode the application is currenty in pub mode: Mode, diff --git a/src/tabs.rs b/src/tabs.rs new file mode 100644 index 0000000..c8671e0 --- /dev/null +++ b/src/tabs.rs @@ -0,0 +1,47 @@ +use crate::{args::Args, config::Config, status::Status}; + +pub struct Tabs { + pub statuses: Vec<Status>, + pub index: usize, +} + +impl Tabs { + pub fn new(args: Args, config: Config, height: usize) -> Self { + let status = Status::new(args, config, height); + + Self { + statuses: vec![status.clone(), status], + index: 0, + } + } + + pub fn is_empty(&self) -> bool { + self.statuses.is_empty() + } + + pub fn len(&self) -> usize { + self.statuses.len() + } + + pub fn next(&mut self) { + if self.is_empty() { + self.index = 0; + } else { + self.index = (self.index + 1) % self.len() + } + } + + pub fn prev(&mut self) { + if self.is_empty() { + self.index = 0 + } else if self.index > 0 { + self.index -= 1; + } else { + self.index = self.len() - 1 + } + } + + pub fn selected(&mut self) -> &mut Status { + &mut self.statuses[self.index] + } +} diff --git a/src/visited.rs b/src/visited.rs index 178bfe1..b488f0e 100644 --- a/src/visited.rs +++ b/src/visited.rs @@ -1,6 +1,6 @@ use std::path::PathBuf; -#[derive(Default)] +#[derive(Default, Clone)] pub struct History { pub visited: Vec<PathBuf>, pub index: usize, |