diff --git a/CHANGELOG.md b/CHANGELOG.md index a1e10e6..99930fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,7 @@ Released on - [Issue 298](https://github.com/veeso/termscp/issues/298): tuirealm 2.x - Fixed some performance issues where sometimes the app froze for a couple of seconds, thanks to this . - [Issue 292](https://github.com/veeso/termscp/issues/292): New version alert was not displayed due to a semver regex issue. +- [Issue 291](https://github.com/veeso/termscp/issues/291): Show `..` directory before all the others in the explorer. If you click on it you'll go the parent directory (same as pressing ``). No, you can't select it for transfers and it's actually been implemented in the worse way possible, because this little change would require a huge refactoring of the explorer component. I promise I will do it one day, but I dunno when. - Logging: filter out messages not related to termscp or remotefs ## 0.15.0 diff --git a/src/ui/activities/filetransfer/components/transfer/file_list.rs b/src/ui/activities/filetransfer/components/transfer/file_list.rs index cdd55cc..c6021b5 100644 --- a/src/ui/activities/filetransfer/components/transfer/file_list.rs +++ b/src/ui/activities/filetransfer/components/transfer/file_list.rs @@ -4,7 +4,7 @@ use tuirealm::command::{Cmd, CmdResult, Direction, Position}; use tuirealm::props::{ - Alignment, AttrValue, Attribute, Borders, Color, Style, Table, TextModifiers, + Alignment, AttrValue, Attribute, Borders, Color, Style, Table, TextModifiers, TextSpan, }; use tuirealm::ratatui::text::{Line, Span}; use tuirealm::ratatui::widgets::{List as TuiList, ListDirection, ListItem, ListState}; @@ -12,6 +12,7 @@ use tuirealm::{MockComponent, Props, State, StateValue}; pub const FILE_LIST_CMD_SELECT_ALL: &str = "A"; pub const FILE_LIST_CMD_DESELECT_ALL: &str = "D"; +const PROP_DOT_DOT: &str = "dot_dot"; /// OwnStates contains states for this component #[derive(Clone, Default)] @@ -22,8 +23,8 @@ struct OwnStates { impl OwnStates { /// Initialize list states - pub fn init_list_states(&mut self, len: usize) { - self.selected = Vec::with_capacity(len); + pub fn init_list_states(&mut self, len: usize, has_dot_dot: bool) { + self.selected = Vec::with_capacity(len + if has_dot_dot { 1 } else { 0 }); self.fix_list_index(); } @@ -107,9 +108,9 @@ impl OwnStates { } /// Select all files - pub fn select_all(&mut self) { + pub fn select_all(&mut self, has_dot_dot: bool) { for i in 0..self.list_len() { - self.select(i); + self.select(i + if has_dot_dot { 1 } else { 0 }); } } @@ -172,6 +173,20 @@ impl FileList { self.attr(Attribute::Content, AttrValue::Table(rows)); self } + + /// If enabled, show `..` entry at the beginning of the list + pub fn dot_dot(mut self, show: bool) -> Self { + self.attr(Attribute::Custom(PROP_DOT_DOT), AttrValue::Flag(show)); + self + } + + /// Returns the value of the `dot_dot` property + fn has_dot_dot(&self) -> bool { + self.props + .get(Attribute::Custom(PROP_DOT_DOT)) + .map(|x| x.unwrap_flag()) + .unwrap_or(false) + } } impl MockComponent for FileList { @@ -193,15 +208,28 @@ impl MockComponent for FileList { .unwrap_flag(); let div = tui_realm_stdlib::utils::get_block(borders, Some(title), focus, None); // Make list entries + let init_table_iter = if self.has_dot_dot() { + vec![vec![TextSpan::from("..")]] + } else { + vec![] + }; + let list_items: Vec = match self .props .get(Attribute::Content) .map(|x| x.unwrap_table()) { - Some(table) => table + Some(table) => init_table_iter .iter() + .chain(table.iter()) .enumerate() .map(|(num, row)| { + let num = if self.has_dot_dot() { + num.checked_sub(1).unwrap_or_default() + } else { + num + }; + let columns: Vec = row .iter() .map(|col| { @@ -255,6 +283,7 @@ impl MockComponent for FileList { Some(line) => line.len(), _ => 0, }, + self.has_dot_dot(), ); self.states.fix_list_index(); } @@ -265,8 +294,16 @@ impl MockComponent for FileList { } fn state(&self) -> State { + if self.has_dot_dot() && self.states.list_index == 0 { + return State::One(StateValue::String("..".to_string())); + } + match self.states.is_selection_empty() { - true => State::One(StateValue::Usize(self.states.list_index())), + true => State::One(StateValue::Usize(if self.has_dot_dot() { + self.states.list_index.checked_sub(1).unwrap_or_default() + } else { + self.states.list_index + })), false => State::Vec( self.states .get_selection() @@ -334,7 +371,7 @@ impl MockComponent for FileList { } } Cmd::Custom(FILE_LIST_CMD_SELECT_ALL) => { - self.states.select_all(); + self.states.select_all(self.has_dot_dot()); CmdResult::None } Cmd::Custom(FILE_LIST_CMD_DESELECT_ALL) => { @@ -342,7 +379,15 @@ impl MockComponent for FileList { CmdResult::None } Cmd::Toggle => { - self.states.toggle_file(self.states.list_index()); + if self.has_dot_dot() && self.states.list_index() == 0 { + return CmdResult::None; + } + + self.states.toggle_file(if self.has_dot_dot() { + self.states.list_index().checked_sub(1).unwrap_or_default() + } else { + self.states.list_index() + }); CmdResult::None } _ => CmdResult::None, diff --git a/src/ui/activities/filetransfer/components/transfer/mod.rs b/src/ui/activities/filetransfer/components/transfer/mod.rs index 8868593..07510bd 100644 --- a/src/ui/activities/filetransfer/components/transfer/mod.rs +++ b/src/ui/activities/filetransfer/components/transfer/mod.rs @@ -363,7 +363,8 @@ impl ExplorerLocal { .foreground(fg) .highlighted_color(hg) .title(title, Alignment::Left) - .rows(files.iter().map(|x| vec![TextSpan::from(x)]).collect()), + .rows(files.iter().map(|x| vec![TextSpan::from(x)]).collect()) + .dot_dot(true), } } } @@ -439,11 +440,23 @@ impl Component for ExplorerLocal { }) => Some(Msg::Transfer(TransferMsg::GoToPreviousDirectory)), Event::Keyboard(KeyEvent { code: Key::Enter, .. - }) => Some(Msg::Transfer(TransferMsg::EnterDirectory)), + }) => { + if matches!(self.component.state(), State::One(StateValue::String(_))) { + Some(Msg::Transfer(TransferMsg::GoToParentDirectory)) + } else { + Some(Msg::Transfer(TransferMsg::EnterDirectory)) + } + } Event::Keyboard(KeyEvent { code: Key::Char(' '), .. - }) => Some(Msg::Transfer(TransferMsg::TransferFile)), + }) => { + if matches!(self.component.state(), State::One(StateValue::String(_))) { + Some(Msg::None) + } else { + Some(Msg::Transfer(TransferMsg::TransferFile)) + } + } Event::Keyboard(KeyEvent { code: Key::Char('a'), modifiers: KeyModifiers::NONE, @@ -559,7 +572,8 @@ impl ExplorerRemote { .foreground(fg) .highlighted_color(hg) .title(title, Alignment::Left) - .rows(files.iter().map(|x| vec![TextSpan::from(x)]).collect()), + .rows(files.iter().map(|x| vec![TextSpan::from(x)]).collect()) + .dot_dot(true), } } } @@ -635,11 +649,23 @@ impl Component for ExplorerRemote { }) => Some(Msg::Transfer(TransferMsg::GoToPreviousDirectory)), Event::Keyboard(KeyEvent { code: Key::Enter, .. - }) => Some(Msg::Transfer(TransferMsg::EnterDirectory)), + }) => { + if matches!(self.component.state(), State::One(StateValue::String(_))) { + Some(Msg::Transfer(TransferMsg::GoToParentDirectory)) + } else { + Some(Msg::Transfer(TransferMsg::EnterDirectory)) + } + } Event::Keyboard(KeyEvent { code: Key::Char(' '), .. - }) => Some(Msg::Transfer(TransferMsg::TransferFile)), + }) => { + if matches!(self.component.state(), State::One(StateValue::String(_))) { + Some(Msg::None) + } else { + Some(Msg::Transfer(TransferMsg::TransferFile)) + } + } Event::Keyboard(KeyEvent { code: Key::Char('a'), modifiers: KeyModifiers::NONE,