feat: search in Recents and Trash
This commit is contained in:
parent
49e3d95e7a
commit
bba95c3fc0
4 changed files with 459 additions and 275 deletions
111
src/app.rs
111
src/app.rs
|
|
@ -94,7 +94,8 @@ use crate::{
|
|||
},
|
||||
spawn_detached::spawn_detached,
|
||||
tab::{
|
||||
self, HOVER_DURATION, HeadingOptions, ItemMetadata, Location, SORT_OPTION_FALLBACK, Tab,
|
||||
self, HOVER_DURATION, HeadingOptions, ItemMetadata, Location, SORT_OPTION_FALLBACK,
|
||||
SearchLocation, Tab,
|
||||
},
|
||||
zoom::{zoom_in_view, zoom_out_view, zoom_to_default},
|
||||
};
|
||||
|
|
@ -1384,7 +1385,9 @@ impl App {
|
|||
.iter()
|
||||
.filter_map(|entity| {
|
||||
let tab = self.tab_model.data::<Tab>(entity)?;
|
||||
(tab.location == Location::Trash).then_some((entity, Location::Trash))
|
||||
tab.location
|
||||
.is_trash()
|
||||
.then_some((entity, tab.location.clone()))
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
|
@ -1395,31 +1398,15 @@ impl App {
|
|||
Task::batch(commands)
|
||||
}
|
||||
|
||||
/// Refresh all tabs that are opened in [`Location::Recents`].
|
||||
fn refresh_recents_tabs(&mut self) -> Task<Message> {
|
||||
let commands: Box<[_]> = self
|
||||
.tab_model
|
||||
.iter()
|
||||
.filter_map(|entity| {
|
||||
let tab = self.tab_model.data::<Tab>(entity)?;
|
||||
(tab.location == Location::Recents).then_some(entity)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let commands = commands
|
||||
.into_iter()
|
||||
.map(|entity| self.update_tab(entity, Location::Recents, None));
|
||||
|
||||
Task::batch(commands)
|
||||
}
|
||||
|
||||
fn rescan_recents(&mut self) -> Task<Message> {
|
||||
let needs_reload: Box<[_]> = self
|
||||
.tab_model
|
||||
.iter()
|
||||
.filter_map(|entity| {
|
||||
let tab = self.tab_model.data::<Tab>(entity)?;
|
||||
(tab.location == Location::Recents).then_some((entity, Location::Recents))
|
||||
tab.location
|
||||
.is_recents()
|
||||
.then_some((entity, tab.location.clone()))
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
|
@ -1453,19 +1440,35 @@ impl App {
|
|||
let mut title_location_opt = None;
|
||||
if let Some(tab) = self.tab_model.data_mut::<Tab>(tab) {
|
||||
let location_opt = match term_opt {
|
||||
Some(term) => tab.location.path_opt().map(|path| {
|
||||
(
|
||||
Location::Search(
|
||||
path.clone(),
|
||||
term,
|
||||
tab.config.show_hidden,
|
||||
Instant::now(),
|
||||
),
|
||||
true,
|
||||
)
|
||||
}),
|
||||
Some(term) => {
|
||||
let search_location = if let Some(path) = tab.location.path_opt() {
|
||||
Some(SearchLocation::Path(path.clone()))
|
||||
} else if tab.location.is_recents() {
|
||||
Some(SearchLocation::Recents)
|
||||
} else if tab.location.is_trash() {
|
||||
Some(SearchLocation::Trash)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
search_location.map(|search_location| {
|
||||
return (
|
||||
Location::Search(
|
||||
search_location,
|
||||
term,
|
||||
tab.config.show_hidden,
|
||||
Instant::now(),
|
||||
),
|
||||
true,
|
||||
);
|
||||
})
|
||||
}
|
||||
None => match &tab.location {
|
||||
Location::Search(path, ..) => Some((Location::Path(path.clone()), false)),
|
||||
Location::Search(search_location, ..) => match search_location {
|
||||
SearchLocation::Path(path) => Some((Location::Path(path.clone()), false)),
|
||||
SearchLocation::Recents => Some((Location::Recents, false)),
|
||||
SearchLocation::Trash => Some((Location::Trash, false)),
|
||||
},
|
||||
_ => None,
|
||||
},
|
||||
};
|
||||
|
|
@ -1625,7 +1628,7 @@ impl App {
|
|||
|
||||
nav_model = nav_model.insert(|b| {
|
||||
b.text(fl!("trash"))
|
||||
.icon(icon::icon(tab::trash_icon_symbolic(16)))
|
||||
.icon(icon::icon(tab::trash_helpers::trash_icon_symbolic(16)))
|
||||
.data(Location::Trash)
|
||||
.divider_above()
|
||||
});
|
||||
|
|
@ -2840,7 +2843,7 @@ impl Application for App {
|
|||
Message::Delete(entity_opt) => {
|
||||
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
|
||||
if let Some(tab) = self.tab_model.data::<Tab>(entity) {
|
||||
if tab.location == Location::Trash {
|
||||
if tab.location.is_trash() {
|
||||
if let Some(items) = tab.items_opt() {
|
||||
let mut trash_items = Vec::new();
|
||||
for item in items {
|
||||
|
|
@ -4056,7 +4059,7 @@ impl Application for App {
|
|||
return self.operation(Operation::RemoveFromRecents { paths });
|
||||
}
|
||||
Message::RescanRecents => {
|
||||
return self.refresh_recents_tabs();
|
||||
return self.rescan_recents();
|
||||
}
|
||||
Message::RescanTrash => {
|
||||
// Update trash icon if empty/full
|
||||
|
|
@ -4066,8 +4069,10 @@ impl Application for App {
|
|||
.is_some_and(|loc| matches!(loc, Location::Trash))
|
||||
});
|
||||
if let Some(entity) = maybe_entity {
|
||||
self.nav_model
|
||||
.icon_set(entity, icon::icon(tab::trash_icon_symbolic(16)));
|
||||
self.nav_model.icon_set(
|
||||
entity,
|
||||
icon::icon(tab::trash_helpers::trash_icon_symbolic(16)),
|
||||
);
|
||||
}
|
||||
|
||||
return Task::batch([self.rescan_trash(), self.update_desktop()]);
|
||||
|
|
@ -4659,17 +4664,17 @@ impl Application for App {
|
|||
Some(
|
||||
Location::Desktop(path, ..)
|
||||
| Location::Path(path)
|
||||
| Location::Search(path, ..),
|
||||
| Location::Search(SearchLocation::Path(path), ..),
|
||||
) => {
|
||||
command.arg(path);
|
||||
}
|
||||
Some(Location::Network(uri, ..)) => {
|
||||
command.arg(uri);
|
||||
}
|
||||
Some(Location::Recents) => {
|
||||
Some(Location::Recents | Location::Search(SearchLocation::Recents, ..)) => {
|
||||
command.arg("--recents");
|
||||
}
|
||||
Some(Location::Trash) => {
|
||||
Some(Location::Trash | Location::Search(SearchLocation::Trash, ..)) => {
|
||||
command.arg("--trash");
|
||||
}
|
||||
None => {}
|
||||
|
|
@ -5915,21 +5920,19 @@ impl Application for App {
|
|||
dialog
|
||||
.control(
|
||||
widget::checkbox(
|
||||
format!("{} ({})" ,fl!("apply-to-all"), *conflict_count),
|
||||
format!("{} ({})", fl!("apply-to-all"), *conflict_count),
|
||||
*apply_to_all,
|
||||
)
|
||||
.on_toggle(
|
||||
|apply_to_all| {
|
||||
Message::DialogUpdate(DialogPage::Replace {
|
||||
from: from.clone(),
|
||||
to: to.clone(),
|
||||
multiple: *multiple,
|
||||
apply_to_all,
|
||||
conflict_count: *conflict_count,
|
||||
tx: tx.clone(),
|
||||
})
|
||||
},
|
||||
),
|
||||
.on_toggle(|apply_to_all| {
|
||||
Message::DialogUpdate(DialogPage::Replace {
|
||||
from: from.clone(),
|
||||
to: to.clone(),
|
||||
multiple: *multiple,
|
||||
apply_to_all,
|
||||
conflict_count: *conflict_count,
|
||||
tx: tx.clone(),
|
||||
})
|
||||
}),
|
||||
)
|
||||
.secondary_action(
|
||||
widget::button::standard(fl!("skip")).on_press(Message::ReplaceResult(
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ use crate::{
|
|||
localize::LANGUAGE_SORTER,
|
||||
menu,
|
||||
mounter::{MOUNTERS, MounterItem, MounterItems, MounterKey, MounterMessage},
|
||||
tab::{self, ItemMetadata, Location, Tab},
|
||||
tab::{self, ItemMetadata, Location, SearchLocation, Tab},
|
||||
zoom::{zoom_in_view, zoom_out_view, zoom_to_default},
|
||||
};
|
||||
|
||||
|
|
@ -780,19 +780,35 @@ impl App {
|
|||
|
||||
fn search_set(&mut self, term_opt: Option<String>) -> Task<Message> {
|
||||
let location_opt = match term_opt {
|
||||
Some(term) => self.tab.location.path_opt().map(|path| {
|
||||
(
|
||||
Location::Search(
|
||||
path.clone(),
|
||||
term,
|
||||
self.tab.config.show_hidden,
|
||||
Instant::now(),
|
||||
),
|
||||
true,
|
||||
)
|
||||
}),
|
||||
Some(term) => {
|
||||
let search_location = if let Some(path) = self.tab.location.path_opt() {
|
||||
Some(SearchLocation::Path(path.clone()))
|
||||
} else if self.tab.location.is_recents() {
|
||||
Some(SearchLocation::Recents)
|
||||
} else if self.tab.location.is_trash() {
|
||||
Some(SearchLocation::Trash)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
search_location.map(|search_location| {
|
||||
return (
|
||||
Location::Search(
|
||||
search_location,
|
||||
term,
|
||||
self.tab.config.show_hidden,
|
||||
Instant::now(),
|
||||
),
|
||||
true,
|
||||
);
|
||||
})
|
||||
}
|
||||
None => match &self.tab.location {
|
||||
Location::Search(path, ..) => Some((Location::Path(path.clone()), false)),
|
||||
Location::Search(search_location, ..) => match search_location {
|
||||
SearchLocation::Path(path) => Some((Location::Path(path.clone()), false)),
|
||||
SearchLocation::Recents => Some((Location::Recents, false)),
|
||||
SearchLocation::Trash => Some((Location::Trash, false)),
|
||||
},
|
||||
_ => None,
|
||||
},
|
||||
};
|
||||
|
|
|
|||
24
src/menu.rs
24
src/menu.rs
|
|
@ -22,7 +22,7 @@ use crate::{
|
|||
app::{Action, Message},
|
||||
config::Config,
|
||||
fl,
|
||||
tab::{self, HeadingOptions, Location, LocationMenuAction, Tab},
|
||||
tab::{self, HeadingOptions, Location, LocationMenuAction, SearchLocation, Tab},
|
||||
};
|
||||
|
||||
static MENU_ID: LazyLock<cosmic::widget::Id> =
|
||||
|
|
@ -138,7 +138,9 @@ pub fn context_menu<'a>(
|
|||
selected_dir += 1;
|
||||
}
|
||||
match &item.location_opt {
|
||||
Some(Location::Trash) => selected_trash_only = true,
|
||||
Some(Location::Trash) | Some(Location::Search(SearchLocation::Trash, ..)) => {
|
||||
selected_trash_only = true
|
||||
}
|
||||
Some(Location::Path(path)) => {
|
||||
if selected == 1
|
||||
&& path.extension().and_then(|s| s.to_str()) == Some("desktop")
|
||||
|
|
@ -174,7 +176,8 @@ pub fn context_menu<'a>(
|
|||
tab::Mode::App | tab::Mode::Desktop,
|
||||
Location::Desktop(..)
|
||||
| Location::Path(..)
|
||||
| Location::Search(..)
|
||||
| Location::Search(SearchLocation::Path(..), ..)
|
||||
| Location::Search(SearchLocation::Recents, ..)
|
||||
| Location::Recents
|
||||
| Location::Network(_, _, Some(_)),
|
||||
) => {
|
||||
|
|
@ -212,7 +215,7 @@ pub fn context_menu<'a>(
|
|||
.push(menu_item(fl!("open-in-terminal"), Action::OpenTerminal).into());
|
||||
}
|
||||
}
|
||||
if matches!(tab.location, Location::Search(..) | Location::Recents) {
|
||||
if tab.location.is_recents() {
|
||||
children.push(
|
||||
menu_item(fl!("open-item-location"), Action::OpenItemLocation).into(),
|
||||
);
|
||||
|
|
@ -255,7 +258,7 @@ pub fn context_menu<'a>(
|
|||
children.push(menu_item(fl!("add-to-sidebar"), Action::AddToSidebar).into());
|
||||
}
|
||||
children.push(divider::horizontal::light().into());
|
||||
if matches!(tab.location, Location::Recents) {
|
||||
if tab.location.is_recents() {
|
||||
children.push(
|
||||
menu_item(fl!("remove-from-recents"), Action::RemoveFromRecents).into(),
|
||||
);
|
||||
|
|
@ -322,7 +325,8 @@ pub fn context_menu<'a>(
|
|||
tab::Mode::Dialog(dialog_kind),
|
||||
Location::Desktop(..)
|
||||
| Location::Path(..)
|
||||
| Location::Search(..)
|
||||
| Location::Search(SearchLocation::Path(..), ..)
|
||||
| Location::Search(SearchLocation::Recents, ..)
|
||||
| Location::Recents
|
||||
| Location::Network(_, _, Some(_)),
|
||||
) => {
|
||||
|
|
@ -330,7 +334,7 @@ pub fn context_menu<'a>(
|
|||
if selected_dir == 1 && selected == 1 || selected_dir == 0 {
|
||||
children.push(menu_item(fl!("open"), Action::Open).into());
|
||||
}
|
||||
if matches!(tab.location, Location::Search(..) | Location::Recents) {
|
||||
if matches!(tab.location, Location::Search(..)) || tab.location.is_recents() {
|
||||
children.push(
|
||||
menu_item(fl!("open-item-location"), Action::OpenItemLocation).into(),
|
||||
);
|
||||
|
|
@ -369,7 +373,7 @@ pub fn context_menu<'a>(
|
|||
children.push(sort_item(fl!("sort-by-size"), HeadingOptions::Size));
|
||||
}
|
||||
}
|
||||
(_, Location::Trash) => {
|
||||
(_, Location::Trash | Location::Search(SearchLocation::Trash, ..)) => {
|
||||
if tab.mode.multiple() {
|
||||
children.push(menu_item(fl!("select-all"), Action::SelectAll).into());
|
||||
}
|
||||
|
|
@ -428,7 +432,7 @@ pub fn dialog_menu(
|
|||
Action::SetSort(sort, dir),
|
||||
)
|
||||
};
|
||||
let in_trash = tab.location == Location::Trash;
|
||||
let in_trash = tab.location.is_trash();
|
||||
|
||||
let mut selected_gallery = 0;
|
||||
if let Some(items) = tab.items_opt() {
|
||||
|
|
@ -578,7 +582,7 @@ pub fn menu_bar<'a>(
|
|||
Action::SetSort(sort, dir),
|
||||
)
|
||||
};
|
||||
let in_trash = tab_opt.is_some_and(|tab| tab.location == Location::Trash);
|
||||
let in_trash = tab_opt.is_some_and(|tab| tab.location.is_trash());
|
||||
|
||||
let mut selected_dir = 0;
|
||||
let mut selected = 0;
|
||||
|
|
|
|||
557
src/tab.rs
557
src/tab.rs
|
|
@ -52,6 +52,7 @@ use icu::{
|
|||
use image::{DynamicImage, ImageDecoder, ImageReader};
|
||||
use jxl_oxide::integration::JxlDecoder;
|
||||
use mime_guess::{Mime, mime};
|
||||
use regex::Regex;
|
||||
use rustc_hash::FxHashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
|
|
@ -72,7 +73,7 @@ use std::{
|
|||
};
|
||||
use tempfile::NamedTempFile;
|
||||
use tokio::sync::mpsc;
|
||||
use trash::TrashItemSize;
|
||||
use trash::{TrashItem, TrashItemMetadata, TrashItemSize};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use crate::{
|
||||
|
|
@ -362,39 +363,6 @@ fn tab_complete(path: &Path) -> Result<Vec<(String, PathBuf)>, Box<dyn Error>> {
|
|||
Ok(completions)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn trash_entries() -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
pub fn trash_entries() -> usize {
|
||||
match trash::os_limited::list() {
|
||||
Ok(entries) => entries.len(),
|
||||
Err(_err) => 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn trash_icon(icon_size: u16) -> widget::icon::Handle {
|
||||
widget::icon::from_name(if trash::os_limited::is_empty().unwrap_or(true) {
|
||||
"user-trash"
|
||||
} else {
|
||||
"user-trash-full"
|
||||
})
|
||||
.size(icon_size)
|
||||
.handle()
|
||||
}
|
||||
|
||||
pub fn trash_icon_symbolic(icon_size: u16) -> widget::icon::Handle {
|
||||
widget::icon::from_name(if trash::os_limited::is_empty().unwrap_or(true) {
|
||||
"user-trash-symbolic"
|
||||
} else {
|
||||
"user-trash-full-symbolic"
|
||||
})
|
||||
.size(icon_size)
|
||||
.handle()
|
||||
}
|
||||
|
||||
//TODO: translate, add more levels?
|
||||
fn format_size(size: u64) -> String {
|
||||
const KB: u64 = 1000;
|
||||
|
|
@ -787,6 +755,13 @@ pub fn item_from_gvfs_info(path: PathBuf, file_info: gio::FileInfo, sizes: IconS
|
|||
}
|
||||
}
|
||||
|
||||
pub fn item_from_search_item(search_item: SearchItem, sizes: IconSizes) -> Item {
|
||||
match search_item {
|
||||
SearchItem::Path(path, name, metadata) => item_from_entry(path, name, metadata, sizes),
|
||||
SearchItem::Trash(entry, metadata) => item_from_trash_entry(entry, metadata, sizes),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn item_from_entry(
|
||||
path: PathBuf,
|
||||
name: String,
|
||||
|
|
@ -910,6 +885,59 @@ pub fn item_from_entry(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn item_from_trash_entry(
|
||||
entry: TrashItem,
|
||||
metadata: TrashItemMetadata,
|
||||
sizes: IconSizes,
|
||||
) -> Item {
|
||||
let original_path = entry.original_path();
|
||||
let name = entry.name.to_string_lossy().into_owned();
|
||||
let display_name = Item::display_name(&name);
|
||||
|
||||
let (mime, icon_handle_grid, icon_handle_list, icon_handle_list_condensed) = match metadata.size
|
||||
{
|
||||
trash::TrashItemSize::Entries(_) => (
|
||||
//TODO: make this a static
|
||||
"inode/directory".parse().unwrap(),
|
||||
folder_icon(&original_path, sizes.grid()),
|
||||
folder_icon(&original_path, sizes.list()),
|
||||
folder_icon(&original_path, sizes.list_condensed()),
|
||||
),
|
||||
trash::TrashItemSize::Bytes(_) => {
|
||||
// This passes remote = true so it does not read from the original path
|
||||
let mime = mime_for_path(&original_path, None, true);
|
||||
(
|
||||
mime.clone(),
|
||||
mime_icon(mime.clone(), sizes.grid()),
|
||||
mime_icon(mime.clone(), sizes.list()),
|
||||
mime_icon(mime, sizes.list_condensed()),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
Item {
|
||||
name,
|
||||
display_name,
|
||||
is_mount_point: false,
|
||||
metadata: ItemMetadata::Trash { metadata, entry },
|
||||
hidden: false,
|
||||
location_opt: None,
|
||||
mime,
|
||||
icon_handle_grid,
|
||||
icon_handle_list,
|
||||
icon_handle_list_condensed,
|
||||
thumbnail_opt: Some(ItemThumbnail::NotImage),
|
||||
button_id: widget::Id::unique(),
|
||||
pos_opt: Cell::new(None),
|
||||
rect_opt: Cell::new(None),
|
||||
selected: false,
|
||||
highlighted: false,
|
||||
overlaps_drag_rect: false,
|
||||
dir_size: DirSize::NotDirectory,
|
||||
cut: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_filename_from_path(path: &Path) -> Result<String, String> {
|
||||
Ok(match path.file_name() {
|
||||
Some(name_os) => name_os
|
||||
|
|
@ -1047,8 +1075,8 @@ pub fn scan_path(tab_path: &PathBuf, sizes: IconSizes) -> Vec<Item> {
|
|||
items
|
||||
}
|
||||
|
||||
pub fn scan_search<F: Fn(&Path, &str, Metadata) -> bool + Sync>(
|
||||
tab_path: &PathBuf,
|
||||
pub fn scan_search<F: Fn(SearchItem) -> bool + Sync>(
|
||||
search_location: &SearchLocation,
|
||||
term: &str,
|
||||
show_hidden: bool,
|
||||
callback: F,
|
||||
|
|
@ -1069,48 +1097,99 @@ pub fn scan_search<F: Fn(&Path, &str, Metadata) -> bool + Sync>(
|
|||
}
|
||||
};
|
||||
|
||||
ignore::WalkBuilder::new(tab_path)
|
||||
.standard_filters(false)
|
||||
.hidden(!show_hidden)
|
||||
//TODO: only use this on supported targets
|
||||
.same_file_system(true)
|
||||
.build_parallel()
|
||||
.run(|| {
|
||||
Box::new(|entry_res| {
|
||||
let Ok(entry) = entry_res else {
|
||||
// Skip invalid entries
|
||||
return ignore::WalkState::Skip;
|
||||
};
|
||||
match search_location {
|
||||
SearchLocation::Path(tab_path) => {
|
||||
ignore::WalkBuilder::new(tab_path)
|
||||
.standard_filters(false)
|
||||
.hidden(!show_hidden)
|
||||
//TODO: only use this on supported targets
|
||||
.same_file_system(true)
|
||||
.build_parallel()
|
||||
.run(|| {
|
||||
Box::new(|entry_res| {
|
||||
let Ok(entry) = entry_res else {
|
||||
// Skip invalid entries
|
||||
return ignore::WalkState::Skip;
|
||||
};
|
||||
|
||||
let Some(file_name) = entry.file_name().to_str() else {
|
||||
// Skip anything with an invalid name
|
||||
return ignore::WalkState::Skip;
|
||||
};
|
||||
let Some(file_name) = entry.file_name().to_str() else {
|
||||
// Skip anything with an invalid name
|
||||
return ignore::WalkState::Skip;
|
||||
};
|
||||
|
||||
if regex.is_match(file_name) {
|
||||
let path = entry.path();
|
||||
if regex.is_match(file_name) {
|
||||
let path = entry.path();
|
||||
|
||||
let metadata = match entry.metadata() {
|
||||
Ok(ok) => ok,
|
||||
Err(err) => {
|
||||
log::warn!(
|
||||
"failed to read metadata for entry at {}: {}",
|
||||
path.display(),
|
||||
err
|
||||
);
|
||||
return ignore::WalkState::Continue;
|
||||
let metadata = match entry.metadata() {
|
||||
Ok(ok) => ok,
|
||||
Err(err) => {
|
||||
log::warn!(
|
||||
"failed to read metadata for entry at {}: {}",
|
||||
path.display(),
|
||||
err
|
||||
);
|
||||
return ignore::WalkState::Continue;
|
||||
}
|
||||
};
|
||||
|
||||
if !callback(SearchItem::Path(
|
||||
path.to_path_buf(),
|
||||
file_name.to_string(),
|
||||
metadata,
|
||||
)) {
|
||||
return ignore::WalkState::Quit;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//TODO: use entry.into_path?
|
||||
if !callback(path, file_name, metadata) {
|
||||
return ignore::WalkState::Quit;
|
||||
ignore::WalkState::Continue
|
||||
})
|
||||
});
|
||||
}
|
||||
SearchLocation::Recents => {
|
||||
let recent_files = match recently_used_xbel::parse_file() {
|
||||
Ok(recent_files) => recent_files,
|
||||
Err(err) => {
|
||||
log::warn!("Error reading recent files: {err:?}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
for bookmark in recent_files.bookmarks {
|
||||
let path = uri_to_path(bookmark.href);
|
||||
if let Some(path) = path
|
||||
&& path.exists()
|
||||
{
|
||||
let file_name = path.file_name();
|
||||
if let Some(file_name) = file_name {
|
||||
let file_name = file_name.to_string_lossy();
|
||||
if regex.is_match(&file_name) {
|
||||
match path.metadata() {
|
||||
Ok(metadata) => {
|
||||
if !callback(SearchItem::Path(
|
||||
path.to_path_buf(),
|
||||
file_name.to_string(),
|
||||
metadata,
|
||||
)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::warn!(
|
||||
"failed to read metadata for entry at {}: {}",
|
||||
path.display(),
|
||||
err
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ignore::WalkState::Continue
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
SearchLocation::Trash => {
|
||||
trash_helpers::scan_search_trash(callback, ®ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This config statement is from trash::os_limited, inverted
|
||||
|
|
@ -1123,9 +1202,31 @@ pub fn scan_search<F: Fn(&Path, &str, Metadata) -> bool + Sync>(
|
|||
not(target_os = "android")
|
||||
)
|
||||
)))]
|
||||
pub fn scan_trash(_sizes: IconSizes) -> Vec<Item> {
|
||||
log::warn!("viewing trash not supported on this platform");
|
||||
Vec::new()
|
||||
mod trash_helpers {
|
||||
use super::*;
|
||||
|
||||
pub fn trash_entries() -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
pub fn trash_icon(icon_size: u16) -> widget::icon::Handle {
|
||||
widget::icon::from_name("user-trash")
|
||||
.size(icon_size)
|
||||
.handle()
|
||||
}
|
||||
|
||||
pub fn trash_icon_symbolic(icon_size: u16) -> widget::icon::Handle {
|
||||
widget::icon::from_name("user-trash-symbolic")
|
||||
.size(icon_size)
|
||||
.handle()
|
||||
}
|
||||
|
||||
pub fn scan_trash(_sizes: IconSizes) -> Vec<Item> {
|
||||
log::warn!("viewing trash not supported on this platform");
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
pub fn scan_search_trash<F: Fn(SearchItem) -> bool + Sync>(callback: F, regex: &Regex) {}
|
||||
}
|
||||
|
||||
// This config statement is from trash::os_limited
|
||||
|
|
@ -1138,76 +1239,85 @@ pub fn scan_trash(_sizes: IconSizes) -> Vec<Item> {
|
|||
not(target_os = "android")
|
||||
)
|
||||
))]
|
||||
pub fn scan_trash(sizes: IconSizes) -> Vec<Item> {
|
||||
let entries = match trash::os_limited::list() {
|
||||
Ok(entry) => entry,
|
||||
Err(err) => {
|
||||
log::warn!("failed to read trash items: {err}");
|
||||
return Vec::new();
|
||||
pub mod trash_helpers {
|
||||
use super::*;
|
||||
|
||||
pub fn trash_entries() -> usize {
|
||||
match trash::os_limited::list() {
|
||||
Ok(entries) => entries.len(),
|
||||
Err(_err) => 0,
|
||||
}
|
||||
};
|
||||
let mut items: Vec<_> = entries
|
||||
.into_iter()
|
||||
.filter_map(|entry| {
|
||||
let metadata = trash::os_limited::metadata(&entry)
|
||||
.inspect_err(|err| {
|
||||
log::warn!("failed to get metadata for trash item {entry:?}: {err}")
|
||||
})
|
||||
.ok()?;
|
||||
let original_path = entry.original_path();
|
||||
let name = entry.name.to_string_lossy().into_owned();
|
||||
let display_name = Item::display_name(&name);
|
||||
}
|
||||
|
||||
let (mime, icon_handle_grid, icon_handle_list, icon_handle_list_condensed) =
|
||||
match metadata.size {
|
||||
trash::TrashItemSize::Entries(_) => (
|
||||
//TODO: make this a static
|
||||
"inode/directory".parse().unwrap(),
|
||||
folder_icon(&original_path, sizes.grid()),
|
||||
folder_icon(&original_path, sizes.list()),
|
||||
folder_icon(&original_path, sizes.list_condensed()),
|
||||
),
|
||||
trash::TrashItemSize::Bytes(_) => {
|
||||
// This passes remote = true so it does not read from the original path
|
||||
let mime = mime_for_path(&original_path, None, true);
|
||||
(
|
||||
mime.clone(),
|
||||
mime_icon(mime.clone(), sizes.grid()),
|
||||
mime_icon(mime.clone(), sizes.list()),
|
||||
mime_icon(mime, sizes.list_condensed()),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
Some(Item {
|
||||
name,
|
||||
display_name,
|
||||
is_mount_point: false,
|
||||
metadata: ItemMetadata::Trash { metadata, entry },
|
||||
hidden: false,
|
||||
location_opt: None,
|
||||
mime,
|
||||
icon_handle_grid,
|
||||
icon_handle_list,
|
||||
icon_handle_list_condensed,
|
||||
thumbnail_opt: Some(ItemThumbnail::NotImage),
|
||||
button_id: widget::Id::unique(),
|
||||
pos_opt: Cell::new(None),
|
||||
rect_opt: Cell::new(None),
|
||||
selected: false,
|
||||
highlighted: false,
|
||||
overlaps_drag_rect: false,
|
||||
dir_size: DirSize::NotDirectory,
|
||||
cut: false,
|
||||
})
|
||||
pub fn trash_icon(icon_size: u16) -> widget::icon::Handle {
|
||||
widget::icon::from_name(if trash::os_limited::is_empty().unwrap_or(true) {
|
||||
"user-trash"
|
||||
} else {
|
||||
"user-trash-full"
|
||||
})
|
||||
.collect();
|
||||
items.sort_by(|a, b| match (a.metadata.is_dir(), b.metadata.is_dir()) {
|
||||
(true, false) => Ordering::Less,
|
||||
(false, true) => Ordering::Greater,
|
||||
_ => LANGUAGE_SORTER.compare(&a.display_name, &b.display_name),
|
||||
});
|
||||
items
|
||||
.size(icon_size)
|
||||
.handle()
|
||||
}
|
||||
|
||||
pub fn trash_icon_symbolic(icon_size: u16) -> widget::icon::Handle {
|
||||
widget::icon::from_name(if trash::os_limited::is_empty().unwrap_or(true) {
|
||||
"user-trash-symbolic"
|
||||
} else {
|
||||
"user-trash-full-symbolic"
|
||||
})
|
||||
.size(icon_size)
|
||||
.handle()
|
||||
}
|
||||
|
||||
pub fn scan_trash(sizes: IconSizes) -> Vec<Item> {
|
||||
let entries = match trash::os_limited::list() {
|
||||
Ok(entry) => entry,
|
||||
Err(err) => {
|
||||
log::warn!("failed to read trash items: {err}");
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
let mut items: Vec<_> = entries
|
||||
.into_iter()
|
||||
.filter_map(|entry| {
|
||||
let metadata = trash::os_limited::metadata(&entry)
|
||||
.inspect_err(|err| {
|
||||
log::warn!("failed to get metadata for trash item {entry:?}: {err}")
|
||||
})
|
||||
.ok()?;
|
||||
Some(item_from_trash_entry(entry, metadata, sizes))
|
||||
})
|
||||
.collect();
|
||||
items.sort_by(|a, b| match (a.metadata.is_dir(), b.metadata.is_dir()) {
|
||||
(true, false) => Ordering::Less,
|
||||
(false, true) => Ordering::Greater,
|
||||
_ => LANGUAGE_SORTER.compare(&a.display_name, &b.display_name),
|
||||
});
|
||||
items
|
||||
}
|
||||
|
||||
pub fn scan_search_trash<F: Fn(SearchItem) -> bool + Sync>(callback: F, regex: &Regex) {
|
||||
let entries = match trash::os_limited::list() {
|
||||
Ok(entries) => entries,
|
||||
Err(err) => {
|
||||
log::warn!("failed to read trash items: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
for entry in entries {
|
||||
if let Ok(metadata) = trash::os_limited::metadata(&entry).inspect_err(|err| {
|
||||
log::warn!("failed to get metadata for trash item {entry:?}: {err}")
|
||||
}) {
|
||||
let name = entry.name.to_string_lossy();
|
||||
if regex.is_match(&name) {
|
||||
if !callback(SearchItem::Trash(entry, metadata)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn uri_to_path(uri: String) -> Option<PathBuf> {
|
||||
|
|
@ -1263,7 +1373,7 @@ pub fn scan_recents(sizes: IconSizes) -> Vec<Item> {
|
|||
let item = item_from_entry(path, name, metadata, sizes);
|
||||
Some((item, last_edit.min(last_visit)))
|
||||
} else {
|
||||
log::warn!("recent file path not exist: {}", path.display());
|
||||
log::warn!("recent file path does not exist: {}", path.display());
|
||||
None
|
||||
}
|
||||
})
|
||||
|
|
@ -1343,15 +1453,15 @@ pub fn scan_desktop(
|
|||
let display_name = Item::display_name(&name);
|
||||
|
||||
let metadata = ItemMetadata::SimpleDir {
|
||||
entries: trash_entries() as u64,
|
||||
entries: trash_helpers::trash_entries() as u64,
|
||||
};
|
||||
|
||||
let (mime, icon_handle_grid, icon_handle_list, icon_handle_list_condensed) = {
|
||||
(
|
||||
"inode/directory".parse().unwrap(),
|
||||
trash_icon(sizes.grid()),
|
||||
trash_icon(sizes.list()),
|
||||
trash_icon(sizes.list_condensed()),
|
||||
trash_helpers::trash_icon(sizes.grid()),
|
||||
trash_helpers::trash_icon(sizes.list()),
|
||||
trash_helpers::trash_icon(sizes.list_condensed()),
|
||||
)
|
||||
};
|
||||
|
||||
|
|
@ -1436,6 +1546,29 @@ impl EditLocation {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum SearchLocation {
|
||||
Path(PathBuf),
|
||||
Recents,
|
||||
Trash,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for SearchLocation {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Path(path) => write!(f, "{}", path.display()),
|
||||
Self::Recents => write!(f, "recents"),
|
||||
Self::Trash => write!(f, "trash"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum SearchItem {
|
||||
Path(PathBuf, String, fs::Metadata),
|
||||
Trash(TrashItem, TrashItemMetadata),
|
||||
}
|
||||
|
||||
impl From<Location> for EditLocation {
|
||||
fn from(location: Location) -> Self {
|
||||
Self {
|
||||
|
|
@ -1452,7 +1585,7 @@ pub enum Location {
|
|||
Network(String, String, Option<PathBuf>),
|
||||
Path(PathBuf),
|
||||
Recents,
|
||||
Search(PathBuf, String, bool, Instant),
|
||||
Search(SearchLocation, String, bool, Instant),
|
||||
Trash,
|
||||
}
|
||||
|
||||
|
|
@ -1465,7 +1598,9 @@ impl std::fmt::Display for Location {
|
|||
Self::Network(uri, ..) => write!(f, "{uri}"),
|
||||
Self::Path(path) => write!(f, "{}", path.display()),
|
||||
Self::Recents => write!(f, "recents"),
|
||||
Self::Search(path, term, ..) => write!(f, "search {} for {}", path.display(), term),
|
||||
Self::Search(location, term, ..) => {
|
||||
write!(f, "search {} for {}", location, term)
|
||||
}
|
||||
Self::Trash => write!(f, "trash"),
|
||||
}
|
||||
}
|
||||
|
|
@ -1514,7 +1649,7 @@ impl Location {
|
|||
match self {
|
||||
Self::Desktop(path, ..) => Some(path),
|
||||
Self::Path(path) => Some(path),
|
||||
Self::Search(path, ..) => Some(path),
|
||||
Self::Search(SearchLocation::Path(path), ..) => Some(path),
|
||||
Self::Network(_, _, path) => path.as_ref(),
|
||||
_ => None,
|
||||
}
|
||||
|
|
@ -1524,7 +1659,7 @@ impl Location {
|
|||
match self {
|
||||
Self::Desktop(path, ..) => Some(path),
|
||||
Self::Path(path) => Some(path),
|
||||
Self::Search(path, ..) => Some(path),
|
||||
Self::Search(SearchLocation::Path(path), ..) => Some(path),
|
||||
Self::Network(_, _, path) => path,
|
||||
_ => None,
|
||||
}
|
||||
|
|
@ -1537,9 +1672,12 @@ impl Location {
|
|||
Self::Desktop(path, display.clone(), *desktop_config)
|
||||
}
|
||||
Self::Path(..) => Self::Path(path),
|
||||
Self::Search(_, term, show_hidden, time) => {
|
||||
Self::Search(path, term.clone(), *show_hidden, *time)
|
||||
}
|
||||
Self::Search(SearchLocation::Path(_), term, show_hidden, time) => Self::Search(
|
||||
SearchLocation::Path(path),
|
||||
term.clone(),
|
||||
*show_hidden,
|
||||
*time,
|
||||
),
|
||||
|
||||
other => other.clone(),
|
||||
}
|
||||
|
|
@ -1563,7 +1701,7 @@ impl Location {
|
|||
// Search is done incrementally
|
||||
Vec::new()
|
||||
}
|
||||
Self::Trash => scan_trash(sizes),
|
||||
Self::Trash => trash_helpers::scan_trash(sizes),
|
||||
Self::Recents => scan_recents(sizes),
|
||||
Self::Network(uri, _, _) => scan_network(uri, sizes),
|
||||
};
|
||||
|
|
@ -1591,9 +1729,14 @@ impl Location {
|
|||
let (name, _) = folder_name(path);
|
||||
name
|
||||
}
|
||||
Self::Search(path, term, ..) => {
|
||||
Self::Search(location, term, ..) => {
|
||||
let name = match location {
|
||||
SearchLocation::Path(path) => folder_name(path).0,
|
||||
SearchLocation::Trash => fl!("trash"),
|
||||
SearchLocation::Recents => fl!("recents"),
|
||||
};
|
||||
|
||||
//TODO: translate
|
||||
let (name, _) = folder_name(path);
|
||||
format!("Search \"{term}\": {name}")
|
||||
}
|
||||
Self::Trash => {
|
||||
|
|
@ -1622,6 +1765,20 @@ impl Location {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn is_trash(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Location::Trash | Location::Search(SearchLocation::Trash, ..)
|
||||
)
|
||||
}
|
||||
|
||||
pub fn is_recents(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Location::Recents | Location::Search(SearchLocation::Recents, ..)
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns true if this location supports paste operations (not Trash)
|
||||
pub fn supports_paste(&self) -> bool {
|
||||
matches!(
|
||||
|
|
@ -2241,9 +2398,9 @@ impl Item {
|
|||
) -> widget::Text<'a, cosmic::Theme, cosmic::Renderer> {
|
||||
widget::text::body(name)
|
||||
.wrapping(text::Wrapping::WordOrGlyph)
|
||||
.ellipsize(text::Ellipsize::Middle(
|
||||
text::EllipsizeHeightLimit::Lines(3),
|
||||
))
|
||||
.ellipsize(text::Ellipsize::Middle(text::EllipsizeHeightLimit::Lines(
|
||||
3,
|
||||
)))
|
||||
}
|
||||
|
||||
/// Text widget for a filename in list view: word-or-glyph wrapping, middle-ellipsized to 1 line.
|
||||
|
|
@ -2252,9 +2409,9 @@ impl Item {
|
|||
) -> widget::Text<'a, cosmic::Theme, cosmic::Renderer> {
|
||||
widget::text::body(name)
|
||||
.wrapping(text::Wrapping::WordOrGlyph)
|
||||
.ellipsize(text::Ellipsize::Middle(
|
||||
text::EllipsizeHeightLimit::Lines(1),
|
||||
))
|
||||
.ellipsize(text::Ellipsize::Middle(text::EllipsizeHeightLimit::Lines(
|
||||
1,
|
||||
)))
|
||||
}
|
||||
|
||||
pub fn path_opt(&self) -> Option<&PathBuf> {
|
||||
|
|
@ -2614,7 +2771,7 @@ impl Mode {
|
|||
}
|
||||
|
||||
struct SearchContext {
|
||||
results_rx: mpsc::Receiver<(PathBuf, String, Metadata)>,
|
||||
results_rx: mpsc::Receiver<SearchItem>,
|
||||
ready: Arc<atomic::AtomicBool>,
|
||||
last_modified_opt: Arc<RwLock<Option<SystemTime>>>,
|
||||
}
|
||||
|
|
@ -4080,21 +4237,26 @@ impl Tab {
|
|||
if let Some(items) = &mut self.items_opt {
|
||||
if finished || context.ready.swap(false, atomic::Ordering::SeqCst) {
|
||||
let duration = Instant::now();
|
||||
while let Ok((path, name, metadata)) = context.results_rx.try_recv() {
|
||||
while let Ok(search_item) = context.results_rx.try_recv() {
|
||||
//TODO: combine this with column_sort logic, they must match!
|
||||
let item_modified = metadata.modified().ok();
|
||||
let index = match items.binary_search_by(|other| {
|
||||
item_modified.cmp(&other.metadata.modified())
|
||||
}) {
|
||||
Ok(index) => index,
|
||||
Err(index) => index,
|
||||
};
|
||||
let index =
|
||||
if let SearchItem::Path(_, _, ref metadata) = search_item {
|
||||
let item_modified = metadata.modified().ok();
|
||||
match items.binary_search_by(|other| {
|
||||
item_modified.cmp(&other.metadata.modified())
|
||||
}) {
|
||||
Ok(index) => index,
|
||||
Err(index) => index,
|
||||
}
|
||||
} else {
|
||||
items.len()
|
||||
};
|
||||
|
||||
if index < MAX_SEARCH_RESULTS {
|
||||
//TODO: use correct IconSizes
|
||||
items.insert(
|
||||
index,
|
||||
item_from_entry(path, name, metadata, IconSizes::default()),
|
||||
);
|
||||
let item =
|
||||
item_from_search_item(search_item, IconSizes::default());
|
||||
items.insert(index, item);
|
||||
}
|
||||
// Ensure that updates make it to the GUI in a timely manner
|
||||
if !finished && duration.elapsed() >= MAX_SEARCH_LATENCY {
|
||||
|
|
@ -4822,7 +4984,7 @@ impl Tab {
|
|||
|
||||
let heading_row = widget::row::with_children([
|
||||
heading_item(fl!("name"), Length::Fill, HeadingOptions::Name),
|
||||
if self.location == Location::Trash {
|
||||
if self.location.is_trash() {
|
||||
heading_item(
|
||||
fl!("trashed-on"),
|
||||
Length::Fixed(modified_width),
|
||||
|
|
@ -4946,7 +5108,9 @@ impl Tab {
|
|||
|
||||
let mut children: Vec<Element<_>> = Vec::new();
|
||||
match &self.location {
|
||||
Location::Desktop(path, ..) | Location::Path(path) | Location::Search(path, ..) => {
|
||||
Location::Desktop(path, ..)
|
||||
| Location::Path(path)
|
||||
| Location::Search(SearchLocation::Path(path), ..) => {
|
||||
let excess_str = "...";
|
||||
let excess_width = text_width_body(excess_str);
|
||||
for (index, ancestor) in path.ancestors().enumerate() {
|
||||
|
|
@ -5033,7 +5197,7 @@ impl Tab {
|
|||
}
|
||||
children.reverse();
|
||||
}
|
||||
Location::Trash => {
|
||||
Location::Trash | Location::Search(SearchLocation::Trash, ..) => {
|
||||
children.push(
|
||||
widget::button::custom(widget::text::heading(fl!("trash")))
|
||||
.padding(space_xxxs)
|
||||
|
|
@ -5042,7 +5206,7 @@ impl Tab {
|
|||
.into(),
|
||||
);
|
||||
}
|
||||
Location::Recents => {
|
||||
Location::Recents | Location::Search(SearchLocation::Recents, ..) => {
|
||||
children.push(
|
||||
widget::button::custom(widget::text::heading(fl!("recents")))
|
||||
.padding(space_xxxs)
|
||||
|
|
@ -5422,9 +5586,9 @@ impl Tab {
|
|||
false,
|
||||
false,
|
||||
)),
|
||||
widget::button::custom(
|
||||
Item::grid_display_name(item.display_name.clone()),
|
||||
)
|
||||
widget::button::custom(Item::grid_display_name(
|
||||
item.display_name.clone(),
|
||||
))
|
||||
.id(item.button_id.clone())
|
||||
.on_press(Message::Click(Some(*i)))
|
||||
.padding([0, space_xxxs])
|
||||
|
|
@ -5978,8 +6142,7 @@ impl Tab {
|
|||
tab_column = tab_column.push(popover);
|
||||
}
|
||||
match &self.location {
|
||||
Location::Recents => {}
|
||||
Location::Trash => {
|
||||
Location::Trash | Location::Search(SearchLocation::Trash, ..) => {
|
||||
if let Some(items) = self.items_opt()
|
||||
&& !items.is_empty()
|
||||
{
|
||||
|
|
@ -6358,9 +6521,9 @@ impl Tab {
|
|||
}
|
||||
|
||||
// Load search items incrementally
|
||||
if let Location::Search(path, term, show_hidden, start) = &self.location {
|
||||
if let Location::Search(search_location, term, show_hidden, start) = &self.location {
|
||||
let location = self.location.clone();
|
||||
let path = path.clone();
|
||||
let search_location = search_location.clone();
|
||||
let term = term.clone();
|
||||
let show_hidden = *show_hidden;
|
||||
let start = *start;
|
||||
|
|
@ -6389,27 +6552,25 @@ impl Tab {
|
|||
let output = output.clone();
|
||||
tokio::task::spawn_blocking(move || {
|
||||
scan_search(
|
||||
&path,
|
||||
&search_location,
|
||||
&term,
|
||||
show_hidden,
|
||||
move |path, name, metadata| -> bool {
|
||||
move |search_item| -> bool {
|
||||
// Don't send if the result is too old
|
||||
if let Some(last_modified) = *last_modified_opt.read().unwrap()
|
||||
{
|
||||
if let Ok(modified) = metadata.modified() {
|
||||
if modified < last_modified {
|
||||
if let SearchItem::Path(_, _, ref metadata) = search_item {
|
||||
if let Ok(modified) = metadata.modified() {
|
||||
if modified < last_modified {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
match results_tx.blocking_send((
|
||||
path.to_path_buf(),
|
||||
name.to_string(),
|
||||
metadata,
|
||||
)) {
|
||||
match results_tx.blocking_send(search_item) {
|
||||
Ok(()) => {
|
||||
if ready.swap(true, atomic::Ordering::SeqCst) {
|
||||
true
|
||||
|
|
@ -6432,7 +6593,7 @@ impl Tab {
|
|||
log::info!(
|
||||
"searched for {:?} in {} in {:?}",
|
||||
term,
|
||||
path.display(),
|
||||
search_location,
|
||||
start.elapsed(),
|
||||
);
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue