mirror of
https://github.com/veeso/termscp.git
synced 2025-12-07 09:36:00 -08:00
280 feature request go to path auto completion (#287)
This commit is contained in:
committed by
GitHub
parent
8e2ffeabce
commit
3f01be3baa
@@ -27,6 +27,7 @@ pub(crate) mod open;
|
||||
mod pending;
|
||||
pub(crate) mod rename;
|
||||
pub(crate) mod save;
|
||||
pub(crate) mod scan;
|
||||
pub(crate) mod submit;
|
||||
pub(crate) mod symlink;
|
||||
pub(crate) mod walkdir;
|
||||
|
||||
19
src/ui/activities/filetransfer/actions/scan.rs
Normal file
19
src/ui/activities/filetransfer/actions/scan.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
use std::path::Path;
|
||||
|
||||
use super::{File, FileTransferActivity};
|
||||
use crate::ui::activities::filetransfer::lib::browser::FileExplorerTab;
|
||||
|
||||
impl FileTransferActivity {
|
||||
pub(crate) fn action_scan(&mut self, p: &Path) -> Result<Vec<File>, String> {
|
||||
match self.browser.tab() {
|
||||
FileExplorerTab::Local | FileExplorerTab::FindLocal => self
|
||||
.host
|
||||
.list_dir(p)
|
||||
.map_err(|e| format!("Failed to list directory: {}", e)),
|
||||
FileExplorerTab::Remote | FileExplorerTab::FindRemote => self
|
||||
.client
|
||||
.list_dir(p)
|
||||
.map_err(|e| format!("Failed to list directory: {}", e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,11 +17,11 @@ mod transfer;
|
||||
pub use misc::FooterBar;
|
||||
pub use popups::{
|
||||
ChmodPopup, CopyPopup, DeletePopup, DisconnectPopup, ErrorPopup, ExecPopup, FatalPopup,
|
||||
FileInfoPopup, FilterPopup, GoToPopup, KeybindingsPopup, MkdirPopup, NewfilePopup,
|
||||
FileInfoPopup, FilterPopup, GotoPopup, KeybindingsPopup, MkdirPopup, NewfilePopup,
|
||||
OpenWithPopup, ProgressBarFull, ProgressBarPartial, QuitPopup, RenamePopup, ReplacePopup,
|
||||
ReplacingFilesListPopup, SaveAsPopup, SortingPopup, StatusBarLocal, StatusBarRemote,
|
||||
SymlinkPopup, SyncBrowsingMkdirPopup, WaitPopup, WalkdirWaitPopup, WatchedPathsList,
|
||||
WatcherPopup,
|
||||
WatcherPopup, ATTR_FILES,
|
||||
};
|
||||
pub use transfer::{ExplorerFind, ExplorerFuzzy, ExplorerLocal, ExplorerRemote};
|
||||
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
//!
|
||||
//! popups components
|
||||
|
||||
mod chmod;
|
||||
mod goto;
|
||||
|
||||
use std::time::UNIX_EPOCH;
|
||||
|
||||
use bytesize::ByteSize;
|
||||
@@ -16,15 +19,13 @@ use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
|
||||
#[cfg(unix)]
|
||||
use users::{get_group_by_gid, get_user_by_uid};
|
||||
|
||||
pub use self::chmod::ChmodPopup;
|
||||
pub use self::goto::{GotoPopup, ATTR_FILES};
|
||||
use super::super::Browser;
|
||||
use super::{Msg, PendingActionMsg, TransferMsg, UiMsg};
|
||||
use crate::explorer::FileSorting;
|
||||
use crate::utils::fmt::fmt_time;
|
||||
|
||||
mod chmod;
|
||||
|
||||
pub use chmod::ChmodPopup;
|
||||
|
||||
#[derive(MockComponent)]
|
||||
pub struct CopyPopup {
|
||||
component: Input,
|
||||
@@ -583,90 +584,6 @@ impl Component<Msg, NoUserEvent> for FileInfoPopup {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
pub struct GoToPopup {
|
||||
component: Input,
|
||||
}
|
||||
|
||||
impl GoToPopup {
|
||||
pub fn new(color: Color) -> Self {
|
||||
Self {
|
||||
component: Input::default()
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.input_type(InputType::Text)
|
||||
.placeholder(
|
||||
"/foo/bar/buzz",
|
||||
Style::default().fg(Color::Rgb(128, 128, 128)),
|
||||
)
|
||||
.title("Go to…", Alignment::Center),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for GoToPopup {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Move(Direction::Left));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Right, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Move(Direction::Right));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Home, ..
|
||||
}) => {
|
||||
self.perform(Cmd::GoTo(Position::Begin));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent { code: Key::End, .. }) => {
|
||||
self.perform(Cmd::GoTo(Position::End));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Delete, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Cancel);
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Backspace,
|
||||
..
|
||||
}) => {
|
||||
self.perform(Cmd::Delete);
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char(ch),
|
||||
..
|
||||
}) => {
|
||||
self.perform(Cmd::Type(ch));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => match self.state() {
|
||||
State::One(StateValue::String(i)) => Some(Msg::Transfer(TransferMsg::GoTo(i))),
|
||||
_ => Some(Msg::None),
|
||||
},
|
||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
||||
Some(Msg::Ui(UiMsg::CloseGotoPopup))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
pub struct KeybindingsPopup {
|
||||
component: List,
|
||||
|
||||
435
src/ui/activities/filetransfer/components/popups/goto.rs
Normal file
435
src/ui/activities/filetransfer/components/popups/goto.rs
Normal file
@@ -0,0 +1,435 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use tui_realm_stdlib::Input;
|
||||
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
|
||||
use tuirealm::event::{Key, KeyEvent};
|
||||
use tuirealm::props::{Alignment, BorderType, Borders, Color, InputType, Style};
|
||||
use tuirealm::{
|
||||
AttrValue, Attribute, Component, Event, MockComponent, NoUserEvent, State, StateValue,
|
||||
};
|
||||
|
||||
use crate::ui::activities::filetransfer::{Msg, TransferMsg, UiMsg};
|
||||
|
||||
pub const ATTR_FILES: &str = "files";
|
||||
|
||||
#[derive(Default)]
|
||||
struct OwnStates {
|
||||
/// Path and name of the files
|
||||
files: Vec<(String, String)>,
|
||||
search: Option<String>,
|
||||
last_suggestion: Option<String>,
|
||||
}
|
||||
|
||||
impl OwnStates {
|
||||
pub fn set_files(&mut self, files: Vec<String>) {
|
||||
self.files = files
|
||||
.into_iter()
|
||||
.map(|f| {
|
||||
(
|
||||
f.clone(),
|
||||
PathBuf::from(&f)
|
||||
.file_name()
|
||||
.map(|x| x.to_string_lossy().to_string())
|
||||
.unwrap_or(f),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
enum Suggestion {
|
||||
/// No suggestion
|
||||
None,
|
||||
/// Suggest a string
|
||||
Suggest(String),
|
||||
/// Rescan at `path` is required to satisfy the user input
|
||||
Rescan(PathBuf),
|
||||
}
|
||||
|
||||
impl From<CmdResult> for Suggestion {
|
||||
fn from(value: CmdResult) -> Self {
|
||||
match value {
|
||||
CmdResult::Batch(v) if v.len() == 1 => {
|
||||
if let CmdResult::Submit(State::One(StateValue::String(s))) = v.first().unwrap() {
|
||||
Suggestion::Suggest(s.clone())
|
||||
} else {
|
||||
Suggestion::None
|
||||
}
|
||||
}
|
||||
CmdResult::Batch(v) if v.len() == 2 => {
|
||||
if let CmdResult::Submit(State::One(StateValue::String(s))) = v.get(1).unwrap() {
|
||||
Suggestion::Rescan(PathBuf::from(s))
|
||||
} else {
|
||||
Suggestion::None
|
||||
}
|
||||
}
|
||||
_ => Suggestion::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Suggestion> for CmdResult {
|
||||
fn from(value: Suggestion) -> Self {
|
||||
match value {
|
||||
Suggestion::None => CmdResult::None,
|
||||
Suggestion::Suggest(s) => {
|
||||
CmdResult::Batch(vec![CmdResult::Submit(State::One(StateValue::String(s)))])
|
||||
}
|
||||
Suggestion::Rescan(p) => CmdResult::Batch(vec![
|
||||
CmdResult::None,
|
||||
CmdResult::Submit(State::One(StateValue::String(
|
||||
p.to_string_lossy().to_string(),
|
||||
))),
|
||||
]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OwnStates {
|
||||
/// Return the current suggestion if any, otherwise return search
|
||||
pub fn computed_search(&self) -> String {
|
||||
match (&self.search, &self.last_suggestion) {
|
||||
(_, Some(s)) => s.clone(),
|
||||
(Some(s), _) => s.clone(),
|
||||
_ => "".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Suggest files based on the input
|
||||
pub fn suggest(&mut self, input: &str) -> Suggestion {
|
||||
debug!(
|
||||
"Suggesting for: {input}; files {files:?}",
|
||||
files = self.files
|
||||
);
|
||||
|
||||
let is_path = PathBuf::from(input).is_absolute();
|
||||
|
||||
// case 1. search if any file starts with the input; get first if suggestion is `None`, otherwise get first after suggestion
|
||||
let suggestions: Vec<&String> = self
|
||||
.files
|
||||
.iter()
|
||||
.filter(|(path, file_name)| {
|
||||
if is_path {
|
||||
path.contains(input)
|
||||
} else {
|
||||
file_name.contains(input)
|
||||
}
|
||||
})
|
||||
.map(|(path, _)| path)
|
||||
.collect();
|
||||
|
||||
debug!("Suggestions for {input}: {:?}", suggestions);
|
||||
|
||||
// case 1. if suggestions not empty; then suggest next
|
||||
if !suggestions.is_empty() {
|
||||
let suggestion;
|
||||
if let Some(last_suggestion) = self.last_suggestion.take() {
|
||||
suggestion = suggestions
|
||||
.iter()
|
||||
.skip_while(|f| **f != &last_suggestion)
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| suggestions.first().unwrap())
|
||||
.to_string();
|
||||
} else {
|
||||
suggestion = suggestions.first().map(|x| x.to_string()).unwrap();
|
||||
}
|
||||
|
||||
debug!("Suggested: {suggestion}");
|
||||
self.last_suggestion = Some(suggestion.clone());
|
||||
|
||||
return Suggestion::Suggest(suggestion);
|
||||
}
|
||||
|
||||
self.last_suggestion = None;
|
||||
|
||||
// case 2. otherwise convert suggest to a path and get the parent
|
||||
// to rescan the files
|
||||
let input_as_path = if input.starts_with('/') {
|
||||
input.to_string()
|
||||
} else {
|
||||
format!("./{}", input)
|
||||
};
|
||||
|
||||
let p = PathBuf::from(input_as_path);
|
||||
let parent = p
|
||||
.parent()
|
||||
.map(|p| p.to_path_buf())
|
||||
.unwrap_or_else(|| PathBuf::from("/"));
|
||||
|
||||
// if path is `.`, then return None
|
||||
if parent == PathBuf::from(".") {
|
||||
return Suggestion::None;
|
||||
}
|
||||
|
||||
debug!("Rescan required at: {}", parent.display());
|
||||
|
||||
Suggestion::Rescan(parent)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GotoPopup {
|
||||
input: Input,
|
||||
states: OwnStates,
|
||||
}
|
||||
|
||||
impl GotoPopup {
|
||||
pub fn new(color: Color, files: Vec<String>) -> Self {
|
||||
let mut states = OwnStates::default();
|
||||
states.set_files(files);
|
||||
|
||||
Self {
|
||||
input: Input::default()
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.input_type(InputType::Text)
|
||||
.placeholder(
|
||||
"/foo/bar/buzz",
|
||||
Style::default().fg(Color::Rgb(128, 128, 128)),
|
||||
)
|
||||
.title("Go to… (Press <TAB> for autocompletion)", Alignment::Center),
|
||||
states,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MockComponent for GotoPopup {
|
||||
fn view(&mut self, frame: &mut tuirealm::Frame, area: tuirealm::tui::prelude::Rect) {
|
||||
self.input.view(frame, area);
|
||||
}
|
||||
|
||||
fn attr(&mut self, attr: Attribute, value: AttrValue) {
|
||||
match attr {
|
||||
Attribute::Custom(ATTR_FILES) => {
|
||||
let files = value
|
||||
.unwrap_payload()
|
||||
.unwrap_vec()
|
||||
.into_iter()
|
||||
.map(|x| x.unwrap_str())
|
||||
.collect();
|
||||
|
||||
self.states.set_files(files);
|
||||
// call perform Change
|
||||
self.perform(Cmd::Change);
|
||||
}
|
||||
_ => self.input.attr(attr, value),
|
||||
}
|
||||
}
|
||||
|
||||
fn query(&self, attr: Attribute) -> Option<AttrValue> {
|
||||
self.input.query(attr)
|
||||
}
|
||||
|
||||
fn state(&self) -> State {
|
||||
State::One(StateValue::String(self.states.computed_search()))
|
||||
}
|
||||
|
||||
fn perform(&mut self, cmd: Cmd) -> CmdResult {
|
||||
match cmd {
|
||||
Cmd::Change => {
|
||||
let input = self
|
||||
.states
|
||||
.search
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.unwrap_or_else(|| self.input.state().unwrap_one().unwrap_string());
|
||||
let suggest = self.states.suggest(&input);
|
||||
if let Suggestion::Suggest(suggestion) = suggest.clone() {
|
||||
self.input
|
||||
.attr(Attribute::Value, AttrValue::String(suggestion.clone()));
|
||||
}
|
||||
|
||||
suggest.into()
|
||||
}
|
||||
cmd => {
|
||||
let res = self.input.perform(cmd);
|
||||
if let CmdResult::Changed(State::One(StateValue::String(new_text))) = &res {
|
||||
self.states.search = Some(new_text.clone());
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for GotoPopup {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Move(Direction::Left));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Right, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Move(Direction::Right));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Home, ..
|
||||
}) => {
|
||||
self.perform(Cmd::GoTo(Position::Begin));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent { code: Key::End, .. }) => {
|
||||
self.perform(Cmd::GoTo(Position::End));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Delete, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Cancel);
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Backspace,
|
||||
..
|
||||
}) => {
|
||||
self.perform(Cmd::Delete);
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char(ch),
|
||||
..
|
||||
}) => {
|
||||
self.perform(Cmd::Type(ch));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent { code: Key::Tab, .. }) => {
|
||||
if let Suggestion::Rescan(path) = Suggestion::from(self.perform(Cmd::Change)) {
|
||||
Some(Msg::Transfer(TransferMsg::RescanGotoFiles(path)))
|
||||
} else {
|
||||
Some(Msg::None)
|
||||
}
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => match self.state() {
|
||||
State::One(StateValue::String(i)) => Some(Msg::Transfer(TransferMsg::GoTo(i))),
|
||||
_ => Some(Msg::None),
|
||||
},
|
||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
||||
Some(Msg::Ui(UiMsg::CloseGotoPopup))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_should_convert_from_and_back_cmd_result() {
|
||||
let s = Suggestion::Suggest("foo".to_string());
|
||||
let cmd: CmdResult = s.clone().into();
|
||||
let s2: Suggestion = cmd.into();
|
||||
assert_eq!(s, s2);
|
||||
|
||||
let s = Suggestion::Rescan(PathBuf::from("/foo/bar"));
|
||||
let cmd: CmdResult = s.clone().into();
|
||||
let s2: Suggestion = cmd.into();
|
||||
assert_eq!(s, s2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_should_suggest_next() {
|
||||
let mut states = OwnStates {
|
||||
files: vec![
|
||||
("/home/foo".to_string(), "foo".to_string()),
|
||||
("/home/bar".to_string(), "bar".to_string()),
|
||||
("/home/buzz".to_string(), "buzz".to_string()),
|
||||
("/home/fizz".to_string(), "fizz".to_string()),
|
||||
],
|
||||
search: None,
|
||||
last_suggestion: None,
|
||||
};
|
||||
|
||||
let s = states.suggest("f");
|
||||
assert_eq!(Suggestion::Suggest("/home/foo".to_string()), s);
|
||||
let s = states.suggest("f");
|
||||
assert_eq!(Suggestion::Suggest("/home/fizz".to_string()), s);
|
||||
|
||||
let s = states.suggest("f");
|
||||
assert_eq!(Suggestion::Suggest("/home/foo".to_string()), s);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_should_suggest_absolute_path() {
|
||||
let mut states = OwnStates {
|
||||
files: vec![
|
||||
("/home/foo".to_string(), "foo".to_string()),
|
||||
("/home/bar".to_string(), "bar".to_string()),
|
||||
("/home/buzz".to_string(), "buzz".to_string()),
|
||||
("/home/fizz".to_string(), "fizz".to_string()),
|
||||
],
|
||||
search: None,
|
||||
last_suggestion: None,
|
||||
};
|
||||
|
||||
let s = states.suggest("/home/f");
|
||||
assert_eq!(Suggestion::Suggest("/home/foo".to_string()), s);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_should_suggest_rescan() {
|
||||
let mut states = OwnStates {
|
||||
files: vec![
|
||||
("/home/foo".to_string(), "foo".to_string()),
|
||||
("/home/bar".to_string(), "bar".to_string()),
|
||||
("/home/buzz".to_string(), "buzz".to_string()),
|
||||
("/home/fizz".to_string(), "fizz".to_string()),
|
||||
],
|
||||
search: None,
|
||||
last_suggestion: None,
|
||||
};
|
||||
|
||||
let s = states.suggest("/home/user");
|
||||
assert_eq!(Suggestion::Rescan(PathBuf::from("/home")), s);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_should_suggest_none() {
|
||||
let mut states = OwnStates {
|
||||
files: vec![
|
||||
("/home/foo".to_string(), "foo".to_string()),
|
||||
("/home/bar".to_string(), "bar".to_string()),
|
||||
("/home/buzz".to_string(), "buzz".to_string()),
|
||||
("/home/fizz".to_string(), "fizz".to_string()),
|
||||
],
|
||||
search: None,
|
||||
last_suggestion: None,
|
||||
};
|
||||
|
||||
let s = states.suggest("");
|
||||
assert_eq!(Suggestion::Suggest("/home/foo".to_string()), s);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_should_suggest_none_if_dot() {
|
||||
let mut states = OwnStates {
|
||||
files: vec![
|
||||
("/home/foo".to_string(), "foo".to_string()),
|
||||
("/home/bar".to_string(), "bar".to_string()),
|
||||
("/home/buzz".to_string(), "buzz".to_string()),
|
||||
("/home/fizz".to_string(), "fizz".to_string()),
|
||||
],
|
||||
search: None,
|
||||
last_suggestion: None,
|
||||
};
|
||||
|
||||
let s = states.suggest("./th");
|
||||
assert_eq!(Suggestion::None, s);
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,16 @@ impl Browser {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn explorer(&self) -> &FileExplorer {
|
||||
match self.tab {
|
||||
FileExplorerTab::Local => &self.local,
|
||||
FileExplorerTab::Remote => &self.remote,
|
||||
FileExplorerTab::FindLocal | FileExplorerTab::FindRemote => {
|
||||
self.found.as_ref().map(|x| &x.explorer).unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn local(&self) -> &FileExplorer {
|
||||
&self.local
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ const LOG_CAPACITY: usize = 256;
|
||||
impl FileTransferActivity {
|
||||
/// Call `Application::tick()` and process messages in `Update`
|
||||
pub(super) fn tick(&mut self) {
|
||||
match self.app.tick(PollStrategy::UpTo(3)) {
|
||||
match self.app.tick(PollStrategy::UpTo(1)) {
|
||||
Ok(messages) => {
|
||||
if !messages.is_empty() {
|
||||
self.redraw = true;
|
||||
|
||||
@@ -14,6 +14,7 @@ mod view;
|
||||
|
||||
// locals
|
||||
use std::collections::VecDeque;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
|
||||
// Includes
|
||||
@@ -113,6 +114,7 @@ enum TransferMsg {
|
||||
OpenTextFile,
|
||||
ReloadDir,
|
||||
RenameFile(String),
|
||||
RescanGotoFiles(PathBuf),
|
||||
SaveFileAs(String),
|
||||
ToggleWatch,
|
||||
ToggleWatchFor(usize),
|
||||
|
||||
@@ -324,6 +324,15 @@ impl FileTransferActivity {
|
||||
// Reload files
|
||||
self.update_browser_file_list()
|
||||
}
|
||||
TransferMsg::RescanGotoFiles(path) => {
|
||||
let files = self.action_scan(&path).unwrap_or_default();
|
||||
let files = files
|
||||
.into_iter()
|
||||
.filter(|f| f.is_dir() || f.is_symlink())
|
||||
.map(|f| f.path().to_string_lossy().to_string())
|
||||
.collect();
|
||||
self.update_goto(files);
|
||||
}
|
||||
TransferMsg::SaveFileAs(dest) => {
|
||||
self.umount_saveas();
|
||||
match self.browser.tab() {
|
||||
|
||||
@@ -13,6 +13,7 @@ use tuirealm::{AttrValue, Attribute, Sub, SubClause, SubEventClause};
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use super::browser::{FileExplorerTab, FoundExplorerTab};
|
||||
use super::components::ATTR_FILES;
|
||||
use super::{components, Context, FileTransferActivity, Id};
|
||||
use crate::explorer::FileSorting;
|
||||
use crate::utils::ui::{Popup, Size};
|
||||
@@ -599,18 +600,40 @@ impl FileTransferActivity {
|
||||
}
|
||||
|
||||
pub(super) fn mount_goto(&mut self) {
|
||||
// get files
|
||||
let files = self
|
||||
.browser
|
||||
.explorer()
|
||||
.iter_files()
|
||||
.filter(|f| f.is_dir() || f.is_symlink())
|
||||
.map(|f| f.path().to_string_lossy().to_string())
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let input_color = self.theme().misc_input_dialog;
|
||||
assert!(self
|
||||
.app
|
||||
.remount(
|
||||
Id::GotoPopup,
|
||||
Box::new(components::GoToPopup::new(input_color)),
|
||||
Box::new(components::GotoPopup::new(input_color, files)),
|
||||
vec![],
|
||||
)
|
||||
.is_ok());
|
||||
assert!(self.app.active(&Id::GotoPopup).is_ok());
|
||||
}
|
||||
|
||||
pub(super) fn update_goto(&mut self, files: Vec<String>) {
|
||||
let payload = files
|
||||
.into_iter()
|
||||
.map(PropValue::Str)
|
||||
.collect::<Vec<PropValue>>();
|
||||
|
||||
let _ = self.app.attr(
|
||||
&Id::GotoPopup,
|
||||
Attribute::Custom(ATTR_FILES),
|
||||
AttrValue::Payload(PropPayload::Vec(payload)),
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn umount_goto(&mut self) {
|
||||
let _ = self.app.umount(&Id::GotoPopup);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user