249 feature request better search results (#282)
Some checks failed
Linux / build (push) Has been cancelled
MacOS / build (push) Has been cancelled
Windows / build (push) Has been cancelled

* feat: issue 249 - fuzzy search replaced the old find explorer

* fix: forgot to upload file

* fix: removed debug
This commit is contained in:
Christian Visintin
2024-10-02 17:45:48 +02:00
committed by GitHub
parent c507d54700
commit b2a8a3041c
13 changed files with 625 additions and 226 deletions

View File

@@ -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",
}
)
}

View File

@@ -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()

View File

@@ -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;

View File

@@ -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 => "",
}
}

View File

@@ -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),
}
}
}

View File

@@ -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,

View File

@@ -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(),
);
}
}

View File

@@ -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,

View File

@@ -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(),

View File

@@ -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,
)))),
)),
)),
)),
)),