mirror of
https://github.com/veeso/termscp.git
synced 2025-12-06 17:15:35 -08:00
249 feature request better search results (#282)
* feat: issue 249 - fuzzy search replaced the old find explorer * fix: forgot to upload file * fix: removed debug
This commit is contained in:
committed by
GitHub
parent
c507d54700
commit
b2a8a3041c
@@ -41,8 +41,9 @@
|
||||
|
||||
Released on
|
||||
|
||||
- [Issue 268](https://github.com/veeso/termscp/issues/268): Pods and container explorer for Kube protocol.
|
||||
- BREAKING ‼️ Kube address argument has changed; see manual!
|
||||
- [Issue 249](https://github.com/veeso/termscp/issues/249): The old *find* command has been replaced with a brand new explorer with support to 🪄 **Fuzzy search** 🪄. The command is still `<F>`.
|
||||
- [Issue 268](https://github.com/veeso/termscp/issues/268): 📦 **Pods and container explorer** 🐳 for Kube protocol.
|
||||
- BREAKING ‼️ Kube address argument has changed to `namespace[@<cluster_url>][$<path>]`
|
||||
- Pod and container argumets have been removed; from now on you will connect with the following syntax to the provided namespace: `/pod-name/container-name/path/to/file`
|
||||
- [Issue 279](https://github.com/veeso/termscp/issues/279): do not clear screen
|
||||
- [Issue 277](https://github.com/veeso/termscp/issues/277): Fix a bug in the configuration page, which caused being stuck if the added SSH key was empty
|
||||
|
||||
65
Cargo.lock
generated
65
Cargo.lock
generated
@@ -461,6 +461,25 @@ dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
|
||||
dependencies = [
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.20"
|
||||
@@ -1828,6 +1847,27 @@ dependencies = [
|
||||
"tauri-winrt-notification",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nucleo"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5262af4c94921c2646c5ac6ff7900c2af9cbb08dc26a797e18130a7019c039d4"
|
||||
dependencies = [
|
||||
"nucleo-matcher",
|
||||
"parking_lot 0.12.3",
|
||||
"rayon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nucleo-matcher"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf33f538733d1a5a3494b836ba913207f14d9d4a1d3cd67030c5061bdd2cac85"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num"
|
||||
version = "0.4.3"
|
||||
@@ -2441,6 +2481,26 @@ dependencies = [
|
||||
"unicode-width 0.1.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
|
||||
dependencies = [
|
||||
"either",
|
||||
"rayon-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.16"
|
||||
@@ -3497,6 +3557,7 @@ dependencies = [
|
||||
"magic-crypt",
|
||||
"notify",
|
||||
"notify-rust",
|
||||
"nucleo",
|
||||
"open",
|
||||
"pretty_assertions",
|
||||
"rand",
|
||||
@@ -4167,9 +4228,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wildmatch"
|
||||
version = "2.3.4"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3928939971918220fed093266b809d1ee4ec6c1a2d72692ff6876898f3b16c19"
|
||||
checksum = "68ce1ab1f8c62655ebe1350f589c61e505cf94d385bc6a12899442d9081e71fd"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
|
||||
17
Cargo.toml
17
Cargo.toml
@@ -5,13 +5,7 @@ description = "termscp is a feature rich terminal file transfer and explorer wit
|
||||
edition = "2021"
|
||||
homepage = "https://termscp.veeso.dev"
|
||||
include = ["src/**/*", "LICENSE", "README.md", "CHANGELOG.md"]
|
||||
keywords = [
|
||||
"scp-client",
|
||||
"sftp-client",
|
||||
"ftp-client",
|
||||
"winscp",
|
||||
"command-line-utility",
|
||||
]
|
||||
keywords = ["terminal", "ftp", "scp", "sftp", "tui"]
|
||||
license = "MIT"
|
||||
name = "termscp"
|
||||
readme = "README.md"
|
||||
@@ -46,13 +40,18 @@ dirs = "^5.0"
|
||||
edit = "^0.1"
|
||||
filetime = "^0.2"
|
||||
hostname = "^0.4"
|
||||
keyring = { version = "^3", optional = true, features = ["apple-native", "windows-native", "sync-secret-service"] }
|
||||
keyring = { version = "^3", optional = true, features = [
|
||||
"apple-native",
|
||||
"windows-native",
|
||||
"sync-secret-service",
|
||||
] }
|
||||
lazy-regex = "^3"
|
||||
lazy_static = "^1"
|
||||
log = "^0.4"
|
||||
magic-crypt = "^3"
|
||||
notify = "6"
|
||||
notify-rust = { version = "^4.5", default-features = false, features = ["d"] }
|
||||
nucleo = "0.5"
|
||||
open = "^5.0"
|
||||
rand = "^0.8.5"
|
||||
regex = "^1"
|
||||
@@ -83,7 +82,7 @@ tuirealm = "^1.9"
|
||||
unicode-width = "^0.2"
|
||||
version-compare = "^0.2"
|
||||
whoami = "^1.5"
|
||||
wildmatch = "^2.3"
|
||||
wildmatch = "^2"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "^1"
|
||||
|
||||
@@ -30,6 +30,7 @@ pub enum FileSorting {
|
||||
ModifyTime,
|
||||
CreationTime,
|
||||
Size,
|
||||
None,
|
||||
}
|
||||
|
||||
/// GroupDirs defines how directories should be grouped in sorting files
|
||||
@@ -178,6 +179,7 @@ impl FileExplorer {
|
||||
FileSorting::CreationTime => self.sort_files_by_creation_time(),
|
||||
FileSorting::ModifyTime => self.sort_files_by_mtime(),
|
||||
FileSorting::Size => self.sort_files_by_size(),
|
||||
FileSorting::None => {}
|
||||
}
|
||||
// Directories first (NOTE: MUST COME AFTER OTHER SORTING)
|
||||
// Group directories if necessary
|
||||
@@ -245,6 +247,7 @@ impl std::fmt::Display for FileSorting {
|
||||
FileSorting::ModifyTime => "by_mtime",
|
||||
FileSorting::Name => "by_name",
|
||||
FileSorting::Size => "by_size",
|
||||
FileSorting::None => "none",
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -9,15 +9,15 @@ use super::super::browser::FileExplorerTab;
|
||||
use super::{File, FileTransferActivity, LogLevel, SelectedFile, TransferOpts, TransferPayload};
|
||||
|
||||
impl FileTransferActivity {
|
||||
pub(crate) fn action_local_find(&mut self, input: String) -> Result<Vec<File>, String> {
|
||||
match self.host.find(input.as_str()) {
|
||||
pub(crate) fn action_walkdir_local(&mut self) -> Result<Vec<File>, String> {
|
||||
match self.host.find("*") {
|
||||
Ok(entries) => Ok(entries),
|
||||
Err(err) => Err(format!("Could not search for files: {err}")),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn action_remote_find(&mut self, input: String) -> Result<Vec<File>, String> {
|
||||
match self.client.as_mut().find(input.as_str()) {
|
||||
pub(crate) fn action_walkdir_remote(&mut self) -> Result<Vec<File>, String> {
|
||||
match self.client.as_mut().find("*") {
|
||||
Ok(entries) => Ok(entries),
|
||||
Err(err) => Err(format!("Could not search for files: {err}")),
|
||||
}
|
||||
@@ -26,6 +26,7 @@ impl FileTransferActivity {
|
||||
pub(crate) fn action_find_changedir(&mut self) {
|
||||
// Match entry
|
||||
if let SelectedFile::One(entry) = self.get_found_selected_entries() {
|
||||
debug!("Changedir to: {}", entry.name());
|
||||
// Get path: if a directory, use directory path; if it is a File, get parent path
|
||||
let path = if entry.is_dir() {
|
||||
entry.path().to_path_buf()
|
||||
|
||||
@@ -17,12 +17,12 @@ mod transfer;
|
||||
pub use misc::FooterBar;
|
||||
pub use popups::{
|
||||
ChmodPopup, CopyPopup, DeletePopup, DisconnectPopup, ErrorPopup, ExecPopup, FatalPopup,
|
||||
FileInfoPopup, FilterPopup, FindPopup, GoToPopup, KeybindingsPopup, MkdirPopup, NewfilePopup,
|
||||
FileInfoPopup, FilterPopup, GoToPopup, KeybindingsPopup, MkdirPopup, NewfilePopup,
|
||||
OpenWithPopup, ProgressBarFull, ProgressBarPartial, QuitPopup, RenamePopup, ReplacePopup,
|
||||
ReplacingFilesListPopup, SaveAsPopup, SortingPopup, StatusBarLocal, StatusBarRemote,
|
||||
SymlinkPopup, SyncBrowsingMkdirPopup, WaitPopup, WatchedPathsList, WatcherPopup,
|
||||
};
|
||||
pub use transfer::{ExplorerFind, ExplorerLocal, ExplorerRemote};
|
||||
pub use transfer::{ExplorerFind, ExplorerFuzzy, ExplorerLocal, ExplorerRemote};
|
||||
|
||||
pub use self::log::Log;
|
||||
|
||||
|
||||
@@ -583,89 +583,6 @@ impl Component<Msg, NoUserEvent> for FileInfoPopup {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
pub struct FindPopup {
|
||||
component: Input,
|
||||
}
|
||||
|
||||
impl FindPopup {
|
||||
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("*.txt", Style::default().fg(Color::Rgb(128, 128, 128)))
|
||||
.title("Search files by name or wildmatch", Alignment::Center),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for FindPopup {
|
||||
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::SearchFile(i)))
|
||||
}
|
||||
_ => Some(Msg::None),
|
||||
},
|
||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
||||
Some(Msg::Ui(UiMsg::CloseFindPopup))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
pub struct GoToPopup {
|
||||
component: Input,
|
||||
@@ -1675,6 +1592,7 @@ impl SortingPopup {
|
||||
FileSorting::ModifyTime => 1,
|
||||
FileSorting::Name => 0,
|
||||
FileSorting::Size => 3,
|
||||
FileSorting::None => 0,
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -1778,6 +1696,7 @@ fn file_sorting_label(sorting: FileSorting) -> &'static str {
|
||||
FileSorting::ModifyTime => "By modify time",
|
||||
FileSorting::Name => "By name",
|
||||
FileSorting::Size => "By size",
|
||||
FileSorting::None => "",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
use tui_realm_stdlib::Input;
|
||||
use tuirealm::command::{Cmd, CmdResult};
|
||||
use tuirealm::props::{Alignment, AttrValue, Attribute, Borders, Color, Table};
|
||||
use tuirealm::tui::layout::{Constraint, Direction, Layout};
|
||||
use tuirealm::{MockComponent, State};
|
||||
|
||||
use super::file_list::FileList;
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Focus {
|
||||
List,
|
||||
#[default]
|
||||
Search,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct OwnStates {
|
||||
focus: Focus,
|
||||
}
|
||||
|
||||
impl OwnStates {
|
||||
pub fn next(&mut self) {
|
||||
self.focus = match self.focus {
|
||||
Focus::List => Focus::Search,
|
||||
Focus::Search => Focus::List,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FileListWithSearch {
|
||||
file_list: FileList,
|
||||
search: Input,
|
||||
states: OwnStates,
|
||||
}
|
||||
|
||||
impl FileListWithSearch {
|
||||
pub fn focus(&self) -> Focus {
|
||||
self.states.focus
|
||||
}
|
||||
|
||||
pub fn foreground(mut self, fg: Color) -> Self {
|
||||
self.file_list
|
||||
.attr(Attribute::Foreground, AttrValue::Color(fg));
|
||||
self.search
|
||||
.attr(Attribute::Foreground, AttrValue::Color(fg));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn background(mut self, bg: Color) -> Self {
|
||||
self.file_list
|
||||
.attr(Attribute::Background, AttrValue::Color(bg));
|
||||
self.search
|
||||
.attr(Attribute::Background, AttrValue::Color(bg));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn borders(mut self, b: Borders) -> Self {
|
||||
self.file_list
|
||||
.attr(Attribute::Borders, AttrValue::Borders(b.clone()));
|
||||
self.search.attr(Attribute::Borders, AttrValue::Borders(b));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn title<S: AsRef<str>>(mut self, t: S, a: Alignment) -> Self {
|
||||
self.file_list.attr(
|
||||
Attribute::Title,
|
||||
AttrValue::Title((t.as_ref().to_string(), a)),
|
||||
);
|
||||
self.search.attr(
|
||||
Attribute::Title,
|
||||
AttrValue::Title(("Fuzzy search".to_string(), a)),
|
||||
);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn highlighted_color(mut self, c: Color) -> Self {
|
||||
self.file_list
|
||||
.attr(Attribute::HighlightedColor, AttrValue::Color(c));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn rows(mut self, rows: Table) -> Self {
|
||||
self.file_list
|
||||
.attr(Attribute::Content, AttrValue::Table(rows));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl MockComponent for FileListWithSearch {
|
||||
fn view(&mut self, frame: &mut tuirealm::Frame, area: tuirealm::tui::layout::Rect) {
|
||||
// split the area in two
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Length(3), // Search
|
||||
Constraint::Fill(1), // File list
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.split(area);
|
||||
|
||||
// render the search input
|
||||
self.search.view(frame, chunks[0]);
|
||||
// render the file list
|
||||
self.file_list.view(frame, chunks[1]);
|
||||
}
|
||||
|
||||
fn query(&self, attr: Attribute) -> Option<AttrValue> {
|
||||
self.file_list.query(attr)
|
||||
}
|
||||
|
||||
fn attr(&mut self, attr: Attribute, value: AttrValue) {
|
||||
if attr == Attribute::Focus {
|
||||
let value = value.unwrap_flag();
|
||||
match value {
|
||||
true => self.states.focus = Focus::Search,
|
||||
false => self.states.focus = Focus::List,
|
||||
}
|
||||
self.search.attr(
|
||||
Attribute::Focus,
|
||||
AttrValue::Flag(self.states.focus == Focus::Search),
|
||||
);
|
||||
self.file_list.attr(
|
||||
Attribute::Focus,
|
||||
AttrValue::Flag(self.states.focus == Focus::List),
|
||||
);
|
||||
} else {
|
||||
self.file_list.attr(attr, value);
|
||||
}
|
||||
}
|
||||
|
||||
fn state(&self) -> State {
|
||||
match self.states.focus {
|
||||
Focus::List => self.file_list.state(),
|
||||
Focus::Search => self.search.state(),
|
||||
}
|
||||
}
|
||||
|
||||
fn perform(&mut self, cmd: Cmd) -> CmdResult {
|
||||
match cmd {
|
||||
Cmd::Change => {
|
||||
self.states.next();
|
||||
self.search.attr(
|
||||
Attribute::Focus,
|
||||
AttrValue::Flag(self.states.focus == Focus::Search),
|
||||
);
|
||||
self.file_list.attr(
|
||||
Attribute::Focus,
|
||||
AttrValue::Flag(self.states.focus == Focus::List),
|
||||
);
|
||||
|
||||
CmdResult::None
|
||||
}
|
||||
cmd if self.states.focus == Focus::Search => self.search.perform(cmd),
|
||||
cmd => self.file_list.perform(cmd),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,14 +2,220 @@
|
||||
//!
|
||||
//! file transfer components
|
||||
|
||||
use super::{Msg, TransferMsg, UiMsg};
|
||||
|
||||
mod file_list;
|
||||
use file_list::FileList;
|
||||
use tuirealm::command::{Cmd, Direction, Position};
|
||||
mod file_list_with_search;
|
||||
|
||||
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
|
||||
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
|
||||
use tuirealm::props::{Alignment, Borders, Color, TextSpan};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
|
||||
|
||||
use self::file_list::FileList;
|
||||
use self::file_list_with_search::FileListWithSearch;
|
||||
use super::{Msg, TransferMsg, UiMsg};
|
||||
|
||||
#[derive(MockComponent)]
|
||||
pub struct ExplorerFuzzy {
|
||||
component: FileListWithSearch,
|
||||
}
|
||||
|
||||
impl ExplorerFuzzy {
|
||||
pub fn new<S: AsRef<str>>(title: S, files: &[&str], bg: Color, fg: Color, hg: Color) -> Self {
|
||||
Self {
|
||||
component: FileListWithSearch::default()
|
||||
.background(bg)
|
||||
.borders(Borders::default().color(hg))
|
||||
.foreground(fg)
|
||||
.highlighted_color(hg)
|
||||
.title(title, Alignment::Left)
|
||||
.rows(files.iter().map(|x| vec![TextSpan::from(x)]).collect()),
|
||||
}
|
||||
}
|
||||
|
||||
fn on_search(&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::Tab | Key::Up | Key::Down,
|
||||
..
|
||||
}) => {
|
||||
self.perform(Cmd::Change);
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char(ch),
|
||||
..
|
||||
}) => match self.perform(Cmd::Type(ch)) {
|
||||
CmdResult::Changed(State::One(StateValue::String(search))) => {
|
||||
Some(Msg::Ui(UiMsg::FuzzySearch(search)))
|
||||
}
|
||||
_ => Some(Msg::None),
|
||||
},
|
||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
||||
Some(Msg::Ui(UiMsg::CloseFindExplorer))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn on_file_list(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Down, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Move(Direction::Down));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent { code: Key::Up, .. }) => {
|
||||
self.perform(Cmd::Move(Direction::Up));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::PageDown,
|
||||
..
|
||||
}) => {
|
||||
self.perform(Cmd::Scroll(Direction::Down));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::PageUp, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Scroll(Direction::Up));
|
||||
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::Char('a'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
}) => {
|
||||
let _ = self.perform(Cmd::Custom(file_list::FILE_LIST_CMD_SELECT_ALL));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('a'),
|
||||
modifiers: KeyModifiers::ALT,
|
||||
}) => {
|
||||
let _ = self.perform(Cmd::Custom(file_list::FILE_LIST_CMD_DESELECT_ALL));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('m'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => {
|
||||
let _ = self.perform(Cmd::Toggle);
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent { code: Key::Tab, .. }) => {
|
||||
self.perform(Cmd::Change);
|
||||
Some(Msg::None)
|
||||
}
|
||||
// -- comp msg
|
||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
||||
Some(Msg::Ui(UiMsg::CloseFindExplorer))
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left | Key::Right,
|
||||
..
|
||||
}) => Some(Msg::Ui(UiMsg::ChangeTransferWindow)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => Some(Msg::Transfer(TransferMsg::EnterDirectory)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char(' '),
|
||||
..
|
||||
}) => Some(Msg::Transfer(TransferMsg::TransferFile)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Backspace,
|
||||
..
|
||||
}) => Some(Msg::Transfer(TransferMsg::GoToPreviousDirectory)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('a'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => Some(Msg::Ui(UiMsg::ToggleHiddenFiles)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('b'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => Some(Msg::Ui(UiMsg::ShowFileSortingPopup)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('e') | Key::Delete | Key::Function(8),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => Some(Msg::Ui(UiMsg::ShowDeletePopup)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('i'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => Some(Msg::Ui(UiMsg::ShowFileInfoPopup)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('s') | Key::Function(2),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => Some(Msg::Ui(UiMsg::ShowSaveAsPopup)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('v') | Key::Function(3),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => Some(Msg::Transfer(TransferMsg::OpenFile)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('w'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => Some(Msg::Ui(UiMsg::ShowOpenWithPopup)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('z'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => Some(Msg::Ui(UiMsg::ShowChmodPopup)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for ExplorerFuzzy {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
match self.component.focus() {
|
||||
file_list_with_search::Focus::List => self.on_file_list(ev),
|
||||
file_list_with_search::Focus::Search => self.on_search(ev),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
pub struct ExplorerFind {
|
||||
@@ -261,7 +467,7 @@ impl Component<Msg, NoUserEvent> for ExplorerLocal {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('f'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => Some(Msg::Ui(UiMsg::ShowFindPopup)),
|
||||
}) => Some(Msg::Transfer(TransferMsg::InitFuzzySearch)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('g'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
@@ -457,7 +663,7 @@ impl Component<Msg, NoUserEvent> for ExplorerRemote {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('f'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => Some(Msg::Ui(UiMsg::ShowFindPopup)),
|
||||
}) => Some(Msg::Transfer(TransferMsg::InitFuzzySearch)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('g'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
|
||||
@@ -4,12 +4,15 @@
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use nucleo::Utf32String;
|
||||
use remotefs::File;
|
||||
|
||||
use crate::explorer::builder::FileExplorerBuilder;
|
||||
use crate::explorer::{FileExplorer, FileSorting, GroupDirs};
|
||||
use crate::explorer::{FileExplorer, FileSorting};
|
||||
use crate::system::config_client::ConfigClient;
|
||||
|
||||
const FUZZY_SEARCH_THRESHOLD: u16 = 50;
|
||||
|
||||
/// File explorer tab
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum FileExplorerTab {
|
||||
@@ -28,10 +31,10 @@ pub enum FoundExplorerTab {
|
||||
|
||||
/// Browser contains the browser options
|
||||
pub struct Browser {
|
||||
local: FileExplorer, // Local File explorer state
|
||||
remote: FileExplorer, // Remote File explorer state
|
||||
found: Option<(FoundExplorerTab, FileExplorer)>, // File explorer for find result
|
||||
tab: FileExplorerTab, // Current selected tab
|
||||
local: FileExplorer, // Local File explorer state
|
||||
remote: FileExplorer, // Remote File explorer state
|
||||
found: Option<Found>, // File explorer for find result
|
||||
tab: FileExplorerTab, // Current selected tab
|
||||
pub sync_browsing: bool,
|
||||
}
|
||||
|
||||
@@ -64,17 +67,35 @@ impl Browser {
|
||||
}
|
||||
|
||||
pub fn found(&self) -> Option<&FileExplorer> {
|
||||
self.found.as_ref().map(|x| &x.1)
|
||||
self.found.as_ref().map(|x| &x.explorer)
|
||||
}
|
||||
|
||||
pub fn found_mut(&mut self) -> Option<&mut FileExplorer> {
|
||||
self.found.as_mut().map(|x| &mut x.1)
|
||||
self.found.as_mut().map(|x| &mut x.explorer)
|
||||
}
|
||||
|
||||
/// Perform fuzzy search on found tab
|
||||
pub fn fuzzy_search(&mut self, needle: &str) {
|
||||
if let Some(x) = self.found.as_mut() {
|
||||
x.fuzzy_search(needle)
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize fuzzy search
|
||||
pub fn init_fuzzy_search(&mut self) {
|
||||
if let Some(explorer) = self.found_mut() {
|
||||
explorer.set_files(vec![]);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_found(&mut self, tab: FoundExplorerTab, files: Vec<File>, wrkdir: &Path) {
|
||||
let mut explorer = Self::build_found_explorer(wrkdir);
|
||||
explorer.set_files(files);
|
||||
self.found = Some((tab, explorer));
|
||||
explorer.set_files(files.clone());
|
||||
self.found = Some(Found {
|
||||
tab,
|
||||
explorer,
|
||||
search_results: files,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn del_found(&mut self) {
|
||||
@@ -83,7 +104,7 @@ impl Browser {
|
||||
|
||||
/// Returns found tab if any
|
||||
pub fn found_tab(&self) -> Option<FoundExplorerTab> {
|
||||
self.found.as_ref().map(|x| x.0)
|
||||
self.found.as_ref().map(|x| x.tab)
|
||||
}
|
||||
|
||||
pub fn tab(&self) -> FileExplorerTab {
|
||||
@@ -129,8 +150,8 @@ impl Browser {
|
||||
/// Build explorer reading from `ConfigClient`, for found result (has some differences)
|
||||
fn build_found_explorer(wrkdir: &Path) -> FileExplorer {
|
||||
FileExplorerBuilder::new()
|
||||
.with_file_sorting(FileSorting::Name)
|
||||
.with_group_dirs(Some(GroupDirs::First))
|
||||
.with_file_sorting(FileSorting::None)
|
||||
.with_group_dirs(None)
|
||||
.with_hidden_files(true)
|
||||
.with_stack_size(0)
|
||||
.with_formatter(Some(
|
||||
@@ -139,3 +160,48 @@ impl Browser {
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
/// Found state
|
||||
struct Found {
|
||||
explorer: FileExplorer,
|
||||
/// Search results; original copy of files
|
||||
search_results: Vec<File>,
|
||||
tab: FoundExplorerTab,
|
||||
}
|
||||
|
||||
impl Found {
|
||||
/// Fuzzy search from `search_results` and update `explorer.files` with the results.
|
||||
pub fn fuzzy_search(&mut self, needle: &str) {
|
||||
let search = Utf32String::from(needle);
|
||||
let mut nucleo = nucleo::Matcher::new(nucleo::Config::DEFAULT.match_paths());
|
||||
|
||||
// get scores
|
||||
let mut fuzzy_results_with_score = self
|
||||
.search_results
|
||||
.iter()
|
||||
.map(|f| {
|
||||
(
|
||||
Utf32String::from(f.path().to_string_lossy().into_owned()),
|
||||
f,
|
||||
)
|
||||
})
|
||||
.filter_map(|(path, file)| {
|
||||
nucleo
|
||||
.fuzzy_match(path.slice(..), search.slice(..))
|
||||
.map(|score| (path, file, score))
|
||||
})
|
||||
.filter(|(_, _, score)| *score >= FUZZY_SEARCH_THRESHOLD)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// sort by score; highest first
|
||||
fuzzy_results_with_score.sort_by(|(_, _, a), (_, _, b)| b.cmp(a));
|
||||
|
||||
// update files
|
||||
self.explorer.set_files(
|
||||
fuzzy_results_with_score
|
||||
.into_iter()
|
||||
.map(|(_, file, _)| file.clone())
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,6 @@ enum Id {
|
||||
FatalPopup,
|
||||
FileInfoPopup,
|
||||
FilterPopup,
|
||||
FindPopup,
|
||||
FooterBar,
|
||||
GlobalListener,
|
||||
GotoPopup,
|
||||
@@ -104,6 +103,7 @@ enum TransferMsg {
|
||||
GoTo(String),
|
||||
GoToParentDirectory,
|
||||
GoToPreviousDirectory,
|
||||
InitFuzzySearch,
|
||||
Mkdir(String),
|
||||
NewFile(String),
|
||||
OpenFile,
|
||||
@@ -112,7 +112,6 @@ enum TransferMsg {
|
||||
ReloadDir,
|
||||
RenameFile(String),
|
||||
SaveFileAs(String),
|
||||
SearchFile(String),
|
||||
ToggleWatch,
|
||||
ToggleWatchFor(usize),
|
||||
TransferFile,
|
||||
@@ -133,7 +132,6 @@ enum UiMsg {
|
||||
CloseFileSortingPopup,
|
||||
CloseFilterPopup,
|
||||
CloseFindExplorer,
|
||||
CloseFindPopup,
|
||||
CloseGotoPopup,
|
||||
CloseKeybindingsPopup,
|
||||
CloseMkdirPopup,
|
||||
@@ -147,6 +145,7 @@ enum UiMsg {
|
||||
CloseWatcherPopup,
|
||||
Disconnect,
|
||||
FilterFiles(String),
|
||||
FuzzySearch(String),
|
||||
LogBackTabbed,
|
||||
Quit,
|
||||
ReplacePopupTabbed,
|
||||
@@ -158,7 +157,6 @@ enum UiMsg {
|
||||
ShowFileInfoPopup,
|
||||
ShowFileSortingPopup,
|
||||
ShowFilterPopup,
|
||||
ShowFindPopup,
|
||||
ShowGotoPopup,
|
||||
ShowKeybindingsPopup,
|
||||
ShowLogPanel,
|
||||
|
||||
@@ -211,6 +211,56 @@ impl FileTransferActivity {
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
TransferMsg::InitFuzzySearch => {
|
||||
// Mount wait
|
||||
self.mount_blocking_wait("Scanning current directory…");
|
||||
// Find
|
||||
let res: Result<Vec<File>, String> = match self.browser.tab() {
|
||||
FileExplorerTab::Local => self.action_walkdir_local(),
|
||||
FileExplorerTab::Remote => self.action_walkdir_remote(),
|
||||
_ => panic!("Trying to search for files, while already in a find result"),
|
||||
};
|
||||
// Umount wait
|
||||
self.umount_wait();
|
||||
// Match result
|
||||
match res {
|
||||
Err(err) => {
|
||||
// Mount error
|
||||
self.mount_error(err.as_str());
|
||||
}
|
||||
Ok(files) if files.is_empty() => {
|
||||
// If no file has been found notify user
|
||||
self.mount_info("There are no files in the current directory");
|
||||
}
|
||||
Ok(files) => {
|
||||
// Get wrkdir
|
||||
let wrkdir = match self.browser.tab() {
|
||||
FileExplorerTab::Local => self.local().wrkdir.clone(),
|
||||
_ => self.remote().wrkdir.clone(),
|
||||
};
|
||||
// Create explorer and load files
|
||||
self.browser.set_found(
|
||||
match self.browser.tab() {
|
||||
FileExplorerTab::Local => FoundExplorerTab::Local,
|
||||
_ => FoundExplorerTab::Remote,
|
||||
},
|
||||
files,
|
||||
wrkdir.as_path(),
|
||||
);
|
||||
// init fuzzy search to display nothing
|
||||
self.browser.init_fuzzy_search();
|
||||
// Mount result widget
|
||||
self.mount_find(format!(r#"Searching at "{}""#, wrkdir.display()), true);
|
||||
self.update_find_list();
|
||||
// Initialize tab
|
||||
self.browser.change_tab(match self.browser.tab() {
|
||||
FileExplorerTab::Local => FileExplorerTab::FindLocal,
|
||||
FileExplorerTab::Remote => FileExplorerTab::FindRemote,
|
||||
_ => FileExplorerTab::FindLocal,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
TransferMsg::Mkdir(dir) => {
|
||||
match self.browser.tab() {
|
||||
FileExplorerTab::Local => self.action_local_mkdir(dir),
|
||||
@@ -281,57 +331,7 @@ impl FileTransferActivity {
|
||||
// Reload files
|
||||
self.update_browser_file_list_swapped();
|
||||
}
|
||||
TransferMsg::SearchFile(search) => {
|
||||
self.umount_find_input();
|
||||
// Mount wait
|
||||
self.mount_blocking_wait(format!(r#"Searching for "{search}"…"#).as_str());
|
||||
// Find
|
||||
let res: Result<Vec<File>, String> = match self.browser.tab() {
|
||||
FileExplorerTab::Local => self.action_local_find(search.clone()),
|
||||
FileExplorerTab::Remote => self.action_remote_find(search.clone()),
|
||||
_ => panic!("Trying to search for files, while already in a find result"),
|
||||
};
|
||||
// Umount wait
|
||||
self.umount_wait();
|
||||
// Match result
|
||||
match res {
|
||||
Err(err) => {
|
||||
// Mount error
|
||||
self.mount_error(err.as_str());
|
||||
}
|
||||
Ok(files) if files.is_empty() => {
|
||||
// If no file has been found notify user
|
||||
self.mount_info(
|
||||
format!(r#"Could not find any file matching "{search}""#).as_str(),
|
||||
);
|
||||
}
|
||||
Ok(files) => {
|
||||
// Get wrkdir
|
||||
let wrkdir = match self.browser.tab() {
|
||||
FileExplorerTab::Local => self.local().wrkdir.clone(),
|
||||
_ => self.remote().wrkdir.clone(),
|
||||
};
|
||||
// Create explorer and load files
|
||||
self.browser.set_found(
|
||||
match self.browser.tab() {
|
||||
FileExplorerTab::Local => FoundExplorerTab::Local,
|
||||
_ => FoundExplorerTab::Remote,
|
||||
},
|
||||
files,
|
||||
wrkdir.as_path(),
|
||||
);
|
||||
// Mount result widget
|
||||
self.mount_find(&search);
|
||||
self.update_find_list();
|
||||
// Initialize tab
|
||||
self.browser.change_tab(match self.browser.tab() {
|
||||
FileExplorerTab::Local => FileExplorerTab::FindLocal,
|
||||
FileExplorerTab::Remote => FileExplorerTab::FindRemote,
|
||||
_ => FileExplorerTab::FindLocal,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TransferMsg::ToggleWatch => self.action_toggle_watch(),
|
||||
TransferMsg::ToggleWatchFor(index) => self.action_toggle_watch_for(index),
|
||||
TransferMsg::TransferFile => {
|
||||
@@ -405,7 +405,6 @@ impl FileTransferActivity {
|
||||
self.finalize_find();
|
||||
self.umount_find();
|
||||
}
|
||||
UiMsg::CloseFindPopup => self.umount_find_input(),
|
||||
UiMsg::CloseGotoPopup => self.umount_goto(),
|
||||
UiMsg::CloseKeybindingsPopup => self.umount_help(),
|
||||
UiMsg::CloseMkdirPopup => self.umount_mkdir(),
|
||||
@@ -439,7 +438,7 @@ impl FileTransferActivity {
|
||||
wrkdir.as_path(),
|
||||
);
|
||||
// Mount result widget
|
||||
self.mount_find(&filter);
|
||||
self.mount_find(&filter, false);
|
||||
self.update_find_list();
|
||||
// Initialize tab
|
||||
self.browser.change_tab(match self.browser.tab() {
|
||||
@@ -448,6 +447,10 @@ impl FileTransferActivity {
|
||||
_ => FileExplorerTab::FindLocal,
|
||||
});
|
||||
}
|
||||
UiMsg::FuzzySearch(needle) => {
|
||||
self.browser.fuzzy_search(&needle);
|
||||
self.update_find_list();
|
||||
}
|
||||
UiMsg::ShowLogPanel => {
|
||||
assert!(self.app.active(&Id::Log).is_ok());
|
||||
}
|
||||
@@ -514,7 +517,6 @@ impl FileTransferActivity {
|
||||
}
|
||||
UiMsg::ShowFileSortingPopup => self.mount_file_sorting(),
|
||||
UiMsg::ShowFilterPopup => self.mount_filter(),
|
||||
UiMsg::ShowFindPopup => self.mount_find_input(),
|
||||
UiMsg::ShowGotoPopup => self.mount_goto(),
|
||||
UiMsg::ShowKeybindingsPopup => self.mount_help(),
|
||||
UiMsg::ShowMkdirPopup => self.mount_mkdir(),
|
||||
|
||||
@@ -177,11 +177,6 @@ impl FileTransferActivity {
|
||||
f.render_widget(Clear, popup);
|
||||
// make popup
|
||||
self.app.view(&Id::FilterPopup, f, popup);
|
||||
} else if self.app.mounted(&Id::FindPopup) {
|
||||
let popup = Popup(Size::Percentage(40), Size::Unit(3)).draw_in(f.size());
|
||||
f.render_widget(Clear, popup);
|
||||
// make popup
|
||||
self.app.view(&Id::FindPopup, f, popup);
|
||||
} else if self.app.mounted(&Id::GotoPopup) {
|
||||
let popup = Popup(Size::Percentage(40), Size::Unit(3)).draw_in(f.size());
|
||||
f.render_widget(Clear, popup);
|
||||
@@ -515,7 +510,7 @@ impl FileTransferActivity {
|
||||
let _ = self.app.umount(&Id::ExecPopup);
|
||||
}
|
||||
|
||||
pub(super) fn mount_find(&mut self, search: &str) {
|
||||
pub(super) fn mount_find(&mut self, msg: impl ToString, fuzzy_search: bool) {
|
||||
// Get color
|
||||
let (bg, fg, hg) = match self.browser.tab() {
|
||||
FileExplorerTab::Local | FileExplorerTab::FindLocal => (
|
||||
@@ -529,18 +524,29 @@ impl FileTransferActivity {
|
||||
self.theme().transfer_remote_explorer_highlighted,
|
||||
),
|
||||
};
|
||||
|
||||
// Mount component
|
||||
assert!(self
|
||||
.app
|
||||
.remount(
|
||||
Id::ExplorerFind,
|
||||
Box::new(components::ExplorerFind::new(
|
||||
format!(r#"Search results for "{search}""#),
|
||||
&[],
|
||||
bg,
|
||||
fg,
|
||||
hg
|
||||
)),
|
||||
if fuzzy_search {
|
||||
Box::new(components::ExplorerFuzzy::new(
|
||||
msg.to_string(),
|
||||
&[],
|
||||
bg,
|
||||
fg,
|
||||
hg,
|
||||
))
|
||||
} else {
|
||||
Box::new(components::ExplorerFind::new(
|
||||
msg.to_string(),
|
||||
&[],
|
||||
bg,
|
||||
fg,
|
||||
hg,
|
||||
))
|
||||
},
|
||||
vec![],
|
||||
)
|
||||
.is_ok());
|
||||
@@ -551,24 +557,6 @@ impl FileTransferActivity {
|
||||
let _ = self.app.umount(&Id::ExplorerFind);
|
||||
}
|
||||
|
||||
pub(super) fn mount_find_input(&mut self) {
|
||||
let input_color = self.theme().misc_input_dialog;
|
||||
assert!(self
|
||||
.app
|
||||
.remount(
|
||||
Id::FindPopup,
|
||||
Box::new(components::FindPopup::new(input_color)),
|
||||
vec![],
|
||||
)
|
||||
.is_ok());
|
||||
assert!(self.app.active(&Id::FindPopup).is_ok());
|
||||
}
|
||||
|
||||
pub(super) fn umount_find_input(&mut self) {
|
||||
// Umount input find
|
||||
let _ = self.app.umount(&Id::FindPopup);
|
||||
}
|
||||
|
||||
pub(super) fn mount_goto(&mut self) {
|
||||
let input_color = self.theme().misc_input_dialog;
|
||||
assert!(self
|
||||
@@ -1094,38 +1082,33 @@ impl FileTransferActivity {
|
||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(
|
||||
Id::SortingPopup,
|
||||
)))),
|
||||
Box::new(SubClause::And(
|
||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(
|
||||
Id::FindPopup,
|
||||
)))),
|
||||
Box::new(SubClause::And(
|
||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(
|
||||
Id::SyncBrowsingMkdirPopup,
|
||||
)))),
|
||||
Box::new(SubClause::And(
|
||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(
|
||||
Id::SymlinkPopup,
|
||||
)))),
|
||||
Box::new(SubClause::And(
|
||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(
|
||||
Id::SymlinkPopup,
|
||||
Id::WatcherPopup,
|
||||
)))),
|
||||
Box::new(SubClause::And(
|
||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(
|
||||
Id::WatcherPopup,
|
||||
Id::WatchedPathsList,
|
||||
)))),
|
||||
Box::new(SubClause::And(
|
||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(
|
||||
Id::WatchedPathsList,
|
||||
Id::ChmodPopup,
|
||||
)))),
|
||||
Box::new(SubClause::And(
|
||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(
|
||||
Id::ChmodPopup,
|
||||
Id::WaitPopup,
|
||||
)))),
|
||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(
|
||||
Id::FilterPopup,
|
||||
)))),
|
||||
Box::new(SubClause::And(
|
||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(
|
||||
Id::WaitPopup,
|
||||
)))),
|
||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(
|
||||
Id::FilterPopup,
|
||||
)))),
|
||||
)),
|
||||
)),
|
||||
)),
|
||||
)),
|
||||
|
||||
Reference in New Issue
Block a user