mirror of
https://github.com/veeso/termscp.git
synced 2026-09-28 06:51:20 -07:00
feat: Show .. directory before all the others in the explorer to navigate to the parent dir (#301)
This commit is contained in:
@@ -53,6 +53,7 @@ Released on
|
|||||||
- [Issue 298](https://github.com/veeso/termscp/issues/298): tuirealm 2.x
|
- [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 <https://github.com/veeso/tui-realm/pull/78>.
|
- Fixed some performance issues where sometimes the app froze for a couple of seconds, thanks to this <https://github.com/veeso/tui-realm/pull/78>.
|
||||||
- [Issue 292](https://github.com/veeso/termscp/issues/292): New version alert was not displayed due to a semver regex issue.
|
- [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 `<U>`). 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
|
- Logging: filter out messages not related to termscp or remotefs
|
||||||
|
|
||||||
## 0.15.0
|
## 0.15.0
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
|
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
|
||||||
use tuirealm::props::{
|
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::text::{Line, Span};
|
||||||
use tuirealm::ratatui::widgets::{List as TuiList, ListDirection, ListItem, ListState};
|
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_SELECT_ALL: &str = "A";
|
||||||
pub const FILE_LIST_CMD_DESELECT_ALL: &str = "D";
|
pub const FILE_LIST_CMD_DESELECT_ALL: &str = "D";
|
||||||
|
const PROP_DOT_DOT: &str = "dot_dot";
|
||||||
|
|
||||||
/// OwnStates contains states for this component
|
/// OwnStates contains states for this component
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
@@ -22,8 +23,8 @@ struct OwnStates {
|
|||||||
|
|
||||||
impl OwnStates {
|
impl OwnStates {
|
||||||
/// Initialize list states
|
/// Initialize list states
|
||||||
pub fn init_list_states(&mut self, len: usize) {
|
pub fn init_list_states(&mut self, len: usize, has_dot_dot: bool) {
|
||||||
self.selected = Vec::with_capacity(len);
|
self.selected = Vec::with_capacity(len + if has_dot_dot { 1 } else { 0 });
|
||||||
self.fix_list_index();
|
self.fix_list_index();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,9 +108,9 @@ impl OwnStates {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Select all files
|
/// 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() {
|
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.attr(Attribute::Content, AttrValue::Table(rows));
|
||||||
self
|
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 {
|
impl MockComponent for FileList {
|
||||||
@@ -193,15 +208,28 @@ impl MockComponent for FileList {
|
|||||||
.unwrap_flag();
|
.unwrap_flag();
|
||||||
let div = tui_realm_stdlib::utils::get_block(borders, Some(title), focus, None);
|
let div = tui_realm_stdlib::utils::get_block(borders, Some(title), focus, None);
|
||||||
// Make list entries
|
// Make list entries
|
||||||
|
let init_table_iter = if self.has_dot_dot() {
|
||||||
|
vec![vec![TextSpan::from("..")]]
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
};
|
||||||
|
|
||||||
let list_items: Vec<ListItem> = match self
|
let list_items: Vec<ListItem> = match self
|
||||||
.props
|
.props
|
||||||
.get(Attribute::Content)
|
.get(Attribute::Content)
|
||||||
.map(|x| x.unwrap_table())
|
.map(|x| x.unwrap_table())
|
||||||
{
|
{
|
||||||
Some(table) => table
|
Some(table) => init_table_iter
|
||||||
.iter()
|
.iter()
|
||||||
|
.chain(table.iter())
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(num, row)| {
|
.map(|(num, row)| {
|
||||||
|
let num = if self.has_dot_dot() {
|
||||||
|
num.checked_sub(1).unwrap_or_default()
|
||||||
|
} else {
|
||||||
|
num
|
||||||
|
};
|
||||||
|
|
||||||
let columns: Vec<Span> = row
|
let columns: Vec<Span> = row
|
||||||
.iter()
|
.iter()
|
||||||
.map(|col| {
|
.map(|col| {
|
||||||
@@ -255,6 +283,7 @@ impl MockComponent for FileList {
|
|||||||
Some(line) => line.len(),
|
Some(line) => line.len(),
|
||||||
_ => 0,
|
_ => 0,
|
||||||
},
|
},
|
||||||
|
self.has_dot_dot(),
|
||||||
);
|
);
|
||||||
self.states.fix_list_index();
|
self.states.fix_list_index();
|
||||||
}
|
}
|
||||||
@@ -265,8 +294,16 @@ impl MockComponent for FileList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn state(&self) -> State {
|
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() {
|
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(
|
false => State::Vec(
|
||||||
self.states
|
self.states
|
||||||
.get_selection()
|
.get_selection()
|
||||||
@@ -334,7 +371,7 @@ impl MockComponent for FileList {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Cmd::Custom(FILE_LIST_CMD_SELECT_ALL) => {
|
Cmd::Custom(FILE_LIST_CMD_SELECT_ALL) => {
|
||||||
self.states.select_all();
|
self.states.select_all(self.has_dot_dot());
|
||||||
CmdResult::None
|
CmdResult::None
|
||||||
}
|
}
|
||||||
Cmd::Custom(FILE_LIST_CMD_DESELECT_ALL) => {
|
Cmd::Custom(FILE_LIST_CMD_DESELECT_ALL) => {
|
||||||
@@ -342,7 +379,15 @@ impl MockComponent for FileList {
|
|||||||
CmdResult::None
|
CmdResult::None
|
||||||
}
|
}
|
||||||
Cmd::Toggle => {
|
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
|
||||||
}
|
}
|
||||||
_ => CmdResult::None,
|
_ => CmdResult::None,
|
||||||
|
|||||||
@@ -363,7 +363,8 @@ impl ExplorerLocal {
|
|||||||
.foreground(fg)
|
.foreground(fg)
|
||||||
.highlighted_color(hg)
|
.highlighted_color(hg)
|
||||||
.title(title, Alignment::Left)
|
.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<Msg, NoUserEvent> for ExplorerLocal {
|
|||||||
}) => Some(Msg::Transfer(TransferMsg::GoToPreviousDirectory)),
|
}) => Some(Msg::Transfer(TransferMsg::GoToPreviousDirectory)),
|
||||||
Event::Keyboard(KeyEvent {
|
Event::Keyboard(KeyEvent {
|
||||||
code: Key::Enter, ..
|
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 {
|
Event::Keyboard(KeyEvent {
|
||||||
code: Key::Char(' '),
|
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 {
|
Event::Keyboard(KeyEvent {
|
||||||
code: Key::Char('a'),
|
code: Key::Char('a'),
|
||||||
modifiers: KeyModifiers::NONE,
|
modifiers: KeyModifiers::NONE,
|
||||||
@@ -559,7 +572,8 @@ impl ExplorerRemote {
|
|||||||
.foreground(fg)
|
.foreground(fg)
|
||||||
.highlighted_color(hg)
|
.highlighted_color(hg)
|
||||||
.title(title, Alignment::Left)
|
.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<Msg, NoUserEvent> for ExplorerRemote {
|
|||||||
}) => Some(Msg::Transfer(TransferMsg::GoToPreviousDirectory)),
|
}) => Some(Msg::Transfer(TransferMsg::GoToPreviousDirectory)),
|
||||||
Event::Keyboard(KeyEvent {
|
Event::Keyboard(KeyEvent {
|
||||||
code: Key::Enter, ..
|
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 {
|
Event::Keyboard(KeyEvent {
|
||||||
code: Key::Char(' '),
|
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 {
|
Event::Keyboard(KeyEvent {
|
||||||
code: Key::Char('a'),
|
code: Key::Char('a'),
|
||||||
modifiers: KeyModifiers::NONE,
|
modifiers: KeyModifiers::NONE,
|
||||||
|
|||||||
Reference in New Issue
Block a user