Select result of operation, fixes #500
This commit is contained in:
parent
4ba7d7bbfc
commit
24a7f2bc31
5 changed files with 215 additions and 139 deletions
85
src/app.rs
85
src/app.rs
|
|
@ -64,7 +64,7 @@ use crate::{
|
||||||
localize::LANGUAGE_SORTER,
|
localize::LANGUAGE_SORTER,
|
||||||
menu, mime_app, mime_icon,
|
menu, mime_app, mime_icon,
|
||||||
mounter::{MounterAuth, MounterItem, MounterItems, MounterKey, MounterMessage, MOUNTERS},
|
mounter::{MounterAuth, MounterItem, MounterItems, MounterKey, MounterMessage, MOUNTERS},
|
||||||
operation::{Controller, Operation, ReplaceResult},
|
operation::{Controller, Operation, OperationSelection, ReplaceResult},
|
||||||
spawn_detached::spawn_detached,
|
spawn_detached::spawn_detached,
|
||||||
tab::{self, HeadingOptions, ItemMetadata, Location, Tab, HOVER_DURATION},
|
tab::{self, HeadingOptions, ItemMetadata, Location, Tab, HOVER_DURATION},
|
||||||
};
|
};
|
||||||
|
|
@ -308,7 +308,7 @@ pub enum Message {
|
||||||
PasteContents(PathBuf, ClipboardPaste),
|
PasteContents(PathBuf, ClipboardPaste),
|
||||||
PendingCancel(u64),
|
PendingCancel(u64),
|
||||||
PendingCancelAll,
|
PendingCancelAll,
|
||||||
PendingComplete(u64),
|
PendingComplete(u64, OperationSelection),
|
||||||
PendingDismiss,
|
PendingDismiss,
|
||||||
PendingError(u64, String),
|
PendingError(u64, String),
|
||||||
PendingPause(u64, bool),
|
PendingPause(u64, bool),
|
||||||
|
|
@ -335,7 +335,7 @@ pub enum Message {
|
||||||
Location,
|
Location,
|
||||||
Option<tab::Item>,
|
Option<tab::Item>,
|
||||||
Vec<tab::Item>,
|
Vec<tab::Item>,
|
||||||
Option<PathBuf>,
|
Option<Vec<PathBuf>>,
|
||||||
),
|
),
|
||||||
TabView(Option<Entity>, tab::View),
|
TabView(Option<Entity>, tab::View),
|
||||||
ToggleContextPage(ContextPage),
|
ToggleContextPage(ContextPage),
|
||||||
|
|
@ -655,7 +655,7 @@ impl App {
|
||||||
&mut self,
|
&mut self,
|
||||||
location: Location,
|
location: Location,
|
||||||
activate: bool,
|
activate: bool,
|
||||||
selection_path: Option<PathBuf>,
|
selection_paths: Option<Vec<PathBuf>>,
|
||||||
) -> (Entity, Task<Message>) {
|
) -> (Entity, Task<Message>) {
|
||||||
let mut tab = Tab::new(location.clone(), self.config.tab);
|
let mut tab = Tab::new(location.clone(), self.config.tab);
|
||||||
tab.mode = match self.mode {
|
tab.mode = match self.mode {
|
||||||
|
|
@ -683,7 +683,7 @@ impl App {
|
||||||
Task::batch([
|
Task::batch([
|
||||||
self.update_title(),
|
self.update_title(),
|
||||||
self.update_watcher(),
|
self.update_watcher(),
|
||||||
self.rescan_tab(entity, location, selection_path),
|
self.rescan_tab(entity, location, selection_paths),
|
||||||
]),
|
]),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -692,9 +692,9 @@ impl App {
|
||||||
&mut self,
|
&mut self,
|
||||||
location: Location,
|
location: Location,
|
||||||
activate: bool,
|
activate: bool,
|
||||||
selection_path: Option<PathBuf>,
|
selection_paths: Option<Vec<PathBuf>>,
|
||||||
) -> Task<Message> {
|
) -> Task<Message> {
|
||||||
self.open_tab_entity(location, activate, selection_path).1
|
self.open_tab_entity(location, activate, selection_paths).1
|
||||||
}
|
}
|
||||||
|
|
||||||
fn operation(&mut self, operation: Operation) {
|
fn operation(&mut self, operation: Operation) {
|
||||||
|
|
@ -717,13 +717,38 @@ impl App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rescan_operation_selection(&mut self, op_sel: OperationSelection) -> Task<Message> {
|
||||||
|
log::info!("rescan_operation_selection {:?}", op_sel);
|
||||||
|
let entity = self.tab_model.active();
|
||||||
|
let Some(tab) = self.tab_model.data::<Tab>(entity) else {
|
||||||
|
return Task::none();
|
||||||
|
};
|
||||||
|
let Some(ref items) = tab.items_opt() else {
|
||||||
|
return Task::none();
|
||||||
|
};
|
||||||
|
for item in items.iter() {
|
||||||
|
if item.selected {
|
||||||
|
if let Some(path) = item.path_opt() {
|
||||||
|
if op_sel.selected.contains(path) || op_sel.ignored.contains(path) {
|
||||||
|
// Ignore if path in selected or ignored paths
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return if there is a previous selection not matching
|
||||||
|
return Task::none();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.rescan_tab(entity, tab.location.clone(), Some(op_sel.selected))
|
||||||
|
}
|
||||||
|
|
||||||
fn rescan_tab(
|
fn rescan_tab(
|
||||||
&mut self,
|
&mut self,
|
||||||
entity: Entity,
|
entity: Entity,
|
||||||
location: Location,
|
location: Location,
|
||||||
selection_path: Option<PathBuf>,
|
selection_paths: Option<Vec<PathBuf>>,
|
||||||
) -> Task<Message> {
|
) -> Task<Message> {
|
||||||
log::info!("rescan_tab {entity:?} {location:?} {selection_path:?}");
|
log::info!("rescan_tab {entity:?} {location:?} {selection_paths:?}");
|
||||||
let icon_sizes = self.config.tab.icon_sizes;
|
let icon_sizes = self.config.tab.icon_sizes;
|
||||||
Task::perform(
|
Task::perform(
|
||||||
async move {
|
async move {
|
||||||
|
|
@ -734,7 +759,7 @@ impl App {
|
||||||
location,
|
location,
|
||||||
parent_item_opt,
|
parent_item_opt,
|
||||||
items,
|
items,
|
||||||
selection_path,
|
selection_paths,
|
||||||
)),
|
)),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::warn!("failed to rescan: {}", err);
|
log::warn!("failed to rescan: {}", err);
|
||||||
|
|
@ -2264,7 +2289,7 @@ impl Application for App {
|
||||||
Some(self.open_tab(
|
Some(self.open_tab(
|
||||||
Location::Path(parent.to_path_buf()),
|
Location::Path(parent.to_path_buf()),
|
||||||
true,
|
true,
|
||||||
Some(path),
|
Some(vec![path]),
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
@ -2381,9 +2406,9 @@ impl Application for App {
|
||||||
self.progress_operations.remove(&id);
|
self.progress_operations.remove(&id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::PendingComplete(id) => {
|
Message::PendingComplete(id, op_sel) => {
|
||||||
let mut commands = Vec::with_capacity(3);
|
let mut commands = Vec::with_capacity(4);
|
||||||
|
// Show toast for some operations
|
||||||
if let Some((op, _, _)) = self.pending_operations.remove(&id) {
|
if let Some((op, _, _)) = self.pending_operations.remove(&id) {
|
||||||
if let Some(description) = op.toast() {
|
if let Some(description) = op.toast() {
|
||||||
if let Operation::Delete { ref paths } = op {
|
if let Operation::Delete { ref paths } = op {
|
||||||
|
|
@ -2412,6 +2437,8 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
// Potentially show a notification
|
// Potentially show a notification
|
||||||
commands.push(self.update_notification());
|
commands.push(self.update_notification());
|
||||||
|
// Rescan and select based on operation
|
||||||
|
commands.push(self.rescan_operation_selection(op_sel));
|
||||||
// Manually rescan any trash tabs after any operation is completed
|
// Manually rescan any trash tabs after any operation is completed
|
||||||
commands.push(self.rescan_trash());
|
commands.push(self.rescan_trash());
|
||||||
// if search is active, update "search" tab view
|
// if search is active, update "search" tab view
|
||||||
|
|
@ -2579,7 +2606,7 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::RestoreFromTrash(entity_opt) => {
|
Message::RestoreFromTrash(entity_opt) => {
|
||||||
let mut paths = Vec::new();
|
let mut trash_items = Vec::new();
|
||||||
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
|
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
|
||||||
if let Some(tab) = self.tab_model.data_mut::<Tab>(entity) {
|
if let Some(tab) = self.tab_model.data_mut::<Tab>(entity) {
|
||||||
if let Some(items) = tab.items_opt() {
|
if let Some(items) = tab.items_opt() {
|
||||||
|
|
@ -2587,7 +2614,7 @@ impl Application for App {
|
||||||
if item.selected {
|
if item.selected {
|
||||||
match &item.metadata {
|
match &item.metadata {
|
||||||
ItemMetadata::Trash { entry, .. } => {
|
ItemMetadata::Trash { entry, .. } => {
|
||||||
paths.push(entry.clone());
|
trash_items.push(entry.clone());
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
//TODO: error on trying to restore non-trash file?
|
//TODO: error on trying to restore non-trash file?
|
||||||
|
|
@ -2597,8 +2624,8 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !paths.is_empty() {
|
if !trash_items.is_empty() {
|
||||||
self.operation(Operation::Restore { paths });
|
self.operation(Operation::Restore { items: trash_items });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::SearchActivate => {
|
Message::SearchActivate => {
|
||||||
|
|
@ -2735,14 +2762,14 @@ impl Application for App {
|
||||||
config_set!(favorites, favorites);
|
config_set!(favorites, favorites);
|
||||||
commands.push(self.update_config());
|
commands.push(self.update_config());
|
||||||
}
|
}
|
||||||
tab::Command::ChangeLocation(tab_title, tab_path, selection_path) => {
|
tab::Command::ChangeLocation(tab_title, tab_path, selection_paths) => {
|
||||||
self.activate_nav_model_location(&tab_path);
|
self.activate_nav_model_location(&tab_path);
|
||||||
|
|
||||||
self.tab_model.text_set(entity, tab_title);
|
self.tab_model.text_set(entity, tab_title);
|
||||||
commands.push(Task::batch([
|
commands.push(Task::batch([
|
||||||
self.update_title(),
|
self.update_title(),
|
||||||
self.update_watcher(),
|
self.update_watcher(),
|
||||||
self.rescan_tab(entity, tab_path, selection_path),
|
self.rescan_tab(entity, tab_path, selection_paths),
|
||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
tab::Command::DropFiles(to, from) => {
|
tab::Command::DropFiles(to, from) => {
|
||||||
|
|
@ -2816,14 +2843,14 @@ impl Application for App {
|
||||||
};
|
};
|
||||||
return self.open_tab(location, true, None);
|
return self.open_tab(location, true, None);
|
||||||
}
|
}
|
||||||
Message::TabRescan(entity, location, parent_item_opt, items, selection_path) => {
|
Message::TabRescan(entity, location, parent_item_opt, items, selection_paths) => {
|
||||||
match self.tab_model.data_mut::<Tab>(entity) {
|
match self.tab_model.data_mut::<Tab>(entity) {
|
||||||
Some(tab) => {
|
Some(tab) => {
|
||||||
if location == tab.location {
|
if location == tab.location {
|
||||||
tab.parent_item_opt = parent_item_opt;
|
tab.parent_item_opt = parent_item_opt;
|
||||||
tab.set_items(items);
|
tab.set_items(items);
|
||||||
if let Some(selection_path) = selection_path {
|
if let Some(selection_paths) = selection_paths {
|
||||||
tab.select_path(selection_path);
|
tab.select_paths(selection_paths);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2882,8 +2909,8 @@ impl Application for App {
|
||||||
Message::UndoTrashStart(paths)
|
Message::UndoTrashStart(paths)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Message::UndoTrashStart(paths) => {
|
Message::UndoTrashStart(items) => {
|
||||||
self.operation(Operation::Restore { paths });
|
self.operation(Operation::Restore { items });
|
||||||
}
|
}
|
||||||
Message::WindowClose => {
|
Message::WindowClose => {
|
||||||
if let Some(window_id) = self.window_id_opt.take() {
|
if let Some(window_id) = self.window_id_opt.take() {
|
||||||
|
|
@ -4420,8 +4447,12 @@ impl Application for App {
|
||||||
stream::channel(16, move |msg_tx| async move {
|
stream::channel(16, move |msg_tx| async move {
|
||||||
let msg_tx = Arc::new(tokio::sync::Mutex::new(msg_tx));
|
let msg_tx = Arc::new(tokio::sync::Mutex::new(msg_tx));
|
||||||
match pending_operation.perform(id, &msg_tx, cancelled).await {
|
match pending_operation.perform(id, &msg_tx, cancelled).await {
|
||||||
Ok(()) => {
|
Ok(result_paths) => {
|
||||||
let _ = msg_tx.lock().await.send(Message::PendingComplete(id)).await;
|
let _ = msg_tx
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.send(Message::PendingComplete(id, result_paths))
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
let _ = msg_tx
|
let _ = msg_tx
|
||||||
|
|
|
||||||
|
|
@ -1381,7 +1381,7 @@ impl Application for App {
|
||||||
tab::Command::Action(action) => {
|
tab::Command::Action(action) => {
|
||||||
commands.push(self.update(Message::from(action.message())));
|
commands.push(self.update(Message::from(action.message())));
|
||||||
}
|
}
|
||||||
tab::Command::ChangeLocation(_tab_title, _tab_path, _selection_path) => {
|
tab::Command::ChangeLocation(_tab_title, _tab_path, _selection_paths) => {
|
||||||
commands.push(Task::batch([self.update_watcher(), self.rescan_tab()]));
|
commands.push(Task::batch([self.update_watcher(), self.rescan_tab()]));
|
||||||
}
|
}
|
||||||
tab::Command::Iced(iced_command) => {
|
tab::Command::Iced(iced_command) => {
|
||||||
|
|
|
||||||
|
|
@ -324,55 +324,6 @@ pub enum ReplaceResult {
|
||||||
Cancel,
|
Cancel,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
|
||||||
pub enum Operation {
|
|
||||||
/// Compress files
|
|
||||||
Compress {
|
|
||||||
paths: Vec<PathBuf>,
|
|
||||||
to: PathBuf,
|
|
||||||
archive_type: ArchiveType,
|
|
||||||
},
|
|
||||||
/// Copy items
|
|
||||||
Copy {
|
|
||||||
paths: Vec<PathBuf>,
|
|
||||||
to: PathBuf,
|
|
||||||
},
|
|
||||||
/// Move items to the trash
|
|
||||||
Delete {
|
|
||||||
paths: Vec<PathBuf>,
|
|
||||||
},
|
|
||||||
/// Empty the trash
|
|
||||||
EmptyTrash,
|
|
||||||
/// Uncompress files
|
|
||||||
Extract {
|
|
||||||
paths: Vec<PathBuf>,
|
|
||||||
to: PathBuf,
|
|
||||||
},
|
|
||||||
/// Move items
|
|
||||||
Move {
|
|
||||||
paths: Vec<PathBuf>,
|
|
||||||
to: PathBuf,
|
|
||||||
},
|
|
||||||
NewFile {
|
|
||||||
path: PathBuf,
|
|
||||||
},
|
|
||||||
NewFolder {
|
|
||||||
path: PathBuf,
|
|
||||||
},
|
|
||||||
Rename {
|
|
||||||
from: PathBuf,
|
|
||||||
to: PathBuf,
|
|
||||||
},
|
|
||||||
/// Restore a path from the trash
|
|
||||||
Restore {
|
|
||||||
paths: Vec<trash::TrashItem>,
|
|
||||||
},
|
|
||||||
/// Set executable and launch
|
|
||||||
SetExecutableAndLaunch {
|
|
||||||
path: PathBuf,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn copy_or_move(
|
async fn copy_or_move(
|
||||||
paths: Vec<PathBuf>,
|
paths: Vec<PathBuf>,
|
||||||
to: PathBuf,
|
to: PathBuf,
|
||||||
|
|
@ -380,9 +331,9 @@ async fn copy_or_move(
|
||||||
id: u64,
|
id: u64,
|
||||||
msg_tx: &Arc<TokioMutex<Sender<Message>>>,
|
msg_tx: &Arc<TokioMutex<Sender<Message>>>,
|
||||||
controller: Controller,
|
controller: Controller,
|
||||||
) -> Result<(), String> {
|
) -> Result<OperationSelection, String> {
|
||||||
let msg_tx = msg_tx.clone();
|
let msg_tx = msg_tx.clone();
|
||||||
tokio::task::spawn_blocking(move || -> Result<(), String> {
|
tokio::task::spawn_blocking(move || -> Result<OperationSelection, String> {
|
||||||
log::info!(
|
log::info!(
|
||||||
"{} {:?} to {:?}",
|
"{} {:?} to {:?}",
|
||||||
if moving { "Move" } else { "Copy" },
|
if moving { "Move" } else { "Copy" },
|
||||||
|
|
@ -446,7 +397,7 @@ async fn copy_or_move(
|
||||||
|
|
||||||
context.recursive_copy_or_move(from_to_pairs, moving)?;
|
context.recursive_copy_or_move(from_to_pairs, moving)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(context.op_sel)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(err_str)?
|
.map_err(err_str)?
|
||||||
|
|
@ -553,6 +504,63 @@ fn paths_parent_name<'a>(paths: &'a Vec<PathBuf>) -> Cow<'a, str> {
|
||||||
file_name(parent)
|
file_name(parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct OperationSelection {
|
||||||
|
// Paths to ignore if they are already selected
|
||||||
|
pub ignored: Vec<PathBuf>,
|
||||||
|
// Paths to select
|
||||||
|
pub selected: Vec<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||||
|
pub enum Operation {
|
||||||
|
/// Compress files
|
||||||
|
Compress {
|
||||||
|
paths: Vec<PathBuf>,
|
||||||
|
to: PathBuf,
|
||||||
|
archive_type: ArchiveType,
|
||||||
|
},
|
||||||
|
/// Copy items
|
||||||
|
Copy {
|
||||||
|
paths: Vec<PathBuf>,
|
||||||
|
to: PathBuf,
|
||||||
|
},
|
||||||
|
/// Move items to the trash
|
||||||
|
Delete {
|
||||||
|
paths: Vec<PathBuf>,
|
||||||
|
},
|
||||||
|
/// Empty the trash
|
||||||
|
EmptyTrash,
|
||||||
|
/// Uncompress files
|
||||||
|
Extract {
|
||||||
|
paths: Vec<PathBuf>,
|
||||||
|
to: PathBuf,
|
||||||
|
},
|
||||||
|
/// Move items
|
||||||
|
Move {
|
||||||
|
paths: Vec<PathBuf>,
|
||||||
|
to: PathBuf,
|
||||||
|
},
|
||||||
|
NewFile {
|
||||||
|
path: PathBuf,
|
||||||
|
},
|
||||||
|
NewFolder {
|
||||||
|
path: PathBuf,
|
||||||
|
},
|
||||||
|
Rename {
|
||||||
|
from: PathBuf,
|
||||||
|
to: PathBuf,
|
||||||
|
},
|
||||||
|
/// Restore a path from the trash
|
||||||
|
Restore {
|
||||||
|
items: Vec<trash::TrashItem>,
|
||||||
|
},
|
||||||
|
/// Set executable and launch
|
||||||
|
SetExecutableAndLaunch {
|
||||||
|
path: PathBuf,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
impl Operation {
|
impl Operation {
|
||||||
pub fn pending_text(&self, percent: i32, state: ControllerState) -> String {
|
pub fn pending_text(&self, percent: i32, state: ControllerState) -> String {
|
||||||
let progress = || match state {
|
let progress = || match state {
|
||||||
|
|
@ -610,7 +618,7 @@ impl Operation {
|
||||||
Self::Rename { from, to } => {
|
Self::Rename { from, to } => {
|
||||||
fl!("renaming", from = file_name(from), to = file_name(to))
|
fl!("renaming", from = file_name(from), to = file_name(to))
|
||||||
}
|
}
|
||||||
Self::Restore { paths } => fl!("restoring", items = paths.len(), progress = progress()),
|
Self::Restore { items } => fl!("restoring", items = items.len(), progress = progress()),
|
||||||
Self::SetExecutableAndLaunch { path } => {
|
Self::SetExecutableAndLaunch { path } => {
|
||||||
fl!("setting-executable-and-launching", name = file_name(path))
|
fl!("setting-executable-and-launching", name = file_name(path))
|
||||||
}
|
}
|
||||||
|
|
@ -661,7 +669,7 @@ impl Operation {
|
||||||
parent = parent_name(path)
|
parent = parent_name(path)
|
||||||
),
|
),
|
||||||
Self::Rename { from, to } => fl!("renamed", from = file_name(from), to = file_name(to)),
|
Self::Rename { from, to } => fl!("renamed", from = file_name(from), to = file_name(to)),
|
||||||
Self::Restore { paths } => fl!("restored", items = paths.len()),
|
Self::Restore { items } => fl!("restored", items = items.len()),
|
||||||
Self::SetExecutableAndLaunch { path } => {
|
Self::SetExecutableAndLaunch { path } => {
|
||||||
fl!("set-executable-and-launched", name = file_name(path))
|
fl!("set-executable-and-launched", name = file_name(path))
|
||||||
}
|
}
|
||||||
|
|
@ -701,7 +709,7 @@ impl Operation {
|
||||||
id: u64,
|
id: u64,
|
||||||
msg_tx: &Arc<TokioMutex<Sender<Message>>>,
|
msg_tx: &Arc<TokioMutex<Sender<Message>>>,
|
||||||
controller: Controller,
|
controller: Controller,
|
||||||
) -> Result<(), String> {
|
) -> Result<OperationSelection, String> {
|
||||||
let _ = msg_tx
|
let _ = msg_tx
|
||||||
.lock()
|
.lock()
|
||||||
.await
|
.await
|
||||||
|
|
@ -709,18 +717,23 @@ impl Operation {
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
//TODO: IF ERROR, RETURN AN Operation THAT CAN UNDO THE CURRENT STATE
|
//TODO: IF ERROR, RETURN AN Operation THAT CAN UNDO THE CURRENT STATE
|
||||||
match self {
|
let paths = match self {
|
||||||
Self::Compress {
|
Self::Compress {
|
||||||
paths,
|
paths,
|
||||||
to,
|
to,
|
||||||
archive_type,
|
archive_type,
|
||||||
} => {
|
} => {
|
||||||
let msg_tx = msg_tx.clone();
|
let msg_tx = msg_tx.clone();
|
||||||
tokio::task::spawn_blocking(move || -> Result<(), String> {
|
tokio::task::spawn_blocking(move || -> Result<OperationSelection, String> {
|
||||||
let Some(relative_root) = to.parent() else {
|
let Some(relative_root) = to.parent() else {
|
||||||
return Err(format!("path {:?} has no parent directory", to));
|
return Err(format!("path {:?} has no parent directory", to));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let op_sel = OperationSelection {
|
||||||
|
ignored: paths.clone(),
|
||||||
|
selected: vec![to.clone()],
|
||||||
|
};
|
||||||
|
|
||||||
let mut paths = paths;
|
let mut paths = paths;
|
||||||
for path in paths.clone().iter() {
|
for path in paths.clone().iter() {
|
||||||
if path.is_dir() {
|
if path.is_dir() {
|
||||||
|
|
@ -844,14 +857,14 @@ impl Operation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(op_sel)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(err_str)?
|
.map_err(err_str)?
|
||||||
.map_err(err_str)?;
|
.map_err(err_str)?
|
||||||
}
|
}
|
||||||
Self::Copy { paths, to } => {
|
Self::Copy { paths, to } => {
|
||||||
copy_or_move(paths, to, false, id, msg_tx, controller).await?;
|
copy_or_move(paths, to, false, id, msg_tx, controller).await?
|
||||||
}
|
}
|
||||||
Self::Delete { paths } => {
|
Self::Delete { paths } => {
|
||||||
let total = paths.len();
|
let total = paths.len();
|
||||||
|
|
@ -873,6 +886,7 @@ impl Operation {
|
||||||
.map_err(err_str)?;
|
.map_err(err_str)?;
|
||||||
//TODO: items_opt allows for easy restore
|
//TODO: items_opt allows for easy restore
|
||||||
}
|
}
|
||||||
|
OperationSelection::default()
|
||||||
}
|
}
|
||||||
Self::EmptyTrash => {
|
Self::EmptyTrash => {
|
||||||
#[cfg(any(
|
#[cfg(any(
|
||||||
|
|
@ -908,11 +922,13 @@ impl Operation {
|
||||||
.await
|
.await
|
||||||
.map_err(err_str)??;
|
.map_err(err_str)??;
|
||||||
}
|
}
|
||||||
|
OperationSelection::default()
|
||||||
}
|
}
|
||||||
Self::Extract { paths, to } => {
|
Self::Extract { paths, to } => {
|
||||||
let msg_tx = msg_tx.clone();
|
let msg_tx = msg_tx.clone();
|
||||||
tokio::task::spawn_blocking(move || -> Result<(), String> {
|
tokio::task::spawn_blocking(move || -> Result<OperationSelection, String> {
|
||||||
let total_paths = paths.len();
|
let total_paths = paths.len();
|
||||||
|
let mut op_sel = OperationSelection::default();
|
||||||
for (i, path) in paths.iter().enumerate() {
|
for (i, path) in paths.iter().enumerate() {
|
||||||
controller.check()?;
|
controller.check()?;
|
||||||
|
|
||||||
|
|
@ -937,6 +953,9 @@ impl Operation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
op_sel.ignored.push(path.clone());
|
||||||
|
op_sel.selected.push(new_dir.clone());
|
||||||
|
|
||||||
let msg_tx = msg_tx.clone();
|
let msg_tx = msg_tx.clone();
|
||||||
let controller = controller.clone();
|
let controller = controller.clone();
|
||||||
let mime = mime_for_path(&path);
|
let mime = mime_for_path(&path);
|
||||||
|
|
@ -968,7 +987,7 @@ impl Operation {
|
||||||
.map(io::BufReader::new)
|
.map(io::BufReader::new)
|
||||||
.map(bzip2::read::BzDecoder::new)
|
.map(bzip2::read::BzDecoder::new)
|
||||||
.map(tar::Archive::new)
|
.map(tar::Archive::new)
|
||||||
.and_then(|mut archive| archive.unpack(new_dir))
|
.and_then(|mut archive| archive.unpack(&new_dir))
|
||||||
.map_err(err_str)?
|
.map_err(err_str)?
|
||||||
}
|
}
|
||||||
#[cfg(feature = "liblzma")]
|
#[cfg(feature = "liblzma")]
|
||||||
|
|
@ -977,7 +996,7 @@ impl Operation {
|
||||||
.map(io::BufReader::new)
|
.map(io::BufReader::new)
|
||||||
.map(liblzma::read::XzDecoder::new)
|
.map(liblzma::read::XzDecoder::new)
|
||||||
.map(tar::Archive::new)
|
.map(tar::Archive::new)
|
||||||
.and_then(|mut archive| archive.unpack(new_dir))
|
.and_then(|mut archive| archive.unpack(&new_dir))
|
||||||
.map_err(err_str)?
|
.map_err(err_str)?
|
||||||
}
|
}
|
||||||
_ => Err(format!("unsupported mime type {:?}", mime))?,
|
_ => Err(format!("unsupported mime type {:?}", mime))?,
|
||||||
|
|
@ -985,38 +1004,50 @@ impl Operation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(op_sel)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(err_str)?
|
.map_err(err_str)?
|
||||||
.map_err(err_str)?;
|
.map_err(err_str)?
|
||||||
}
|
}
|
||||||
Self::Move { paths, to } => {
|
Self::Move { paths, to } => {
|
||||||
copy_or_move(paths, to, true, id, msg_tx, controller).await?;
|
copy_or_move(paths, to, true, id, msg_tx, controller).await?
|
||||||
}
|
}
|
||||||
Self::NewFolder { path } => {
|
Self::NewFolder { path } => {
|
||||||
controller.check()?;
|
tokio::task::spawn_blocking(move || -> Result<OperationSelection, String> {
|
||||||
|
controller.check()?;
|
||||||
tokio::task::spawn_blocking(|| fs::create_dir(path))
|
fs::create_dir(&path).map_err(err_str)?;
|
||||||
.await
|
Ok(OperationSelection {
|
||||||
.map_err(err_str)?
|
ignored: Vec::new(),
|
||||||
.map_err(err_str)?;
|
selected: vec![path],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(err_str)??
|
||||||
}
|
}
|
||||||
Self::NewFile { path } => {
|
Self::NewFile { path } => {
|
||||||
controller.check()?;
|
tokio::task::spawn_blocking(move || -> Result<OperationSelection, String> {
|
||||||
|
controller.check()?;
|
||||||
tokio::task::spawn_blocking(|| fs::File::create(path))
|
fs::File::create(&path).map_err(err_str)?;
|
||||||
.await
|
Ok(OperationSelection {
|
||||||
.map_err(err_str)?
|
ignored: Vec::new(),
|
||||||
.map_err(err_str)?;
|
selected: vec![path],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(err_str)??
|
||||||
}
|
}
|
||||||
Self::Rename { from, to } => {
|
Self::Rename { from, to } => {
|
||||||
controller.check()?;
|
tokio::task::spawn_blocking(move || -> Result<OperationSelection, String> {
|
||||||
|
controller.check()?;
|
||||||
tokio::task::spawn_blocking(|| fs::rename(from, to))
|
fs::rename(&from, &to).map_err(err_str)?;
|
||||||
.await
|
Ok(OperationSelection {
|
||||||
.map_err(err_str)?
|
ignored: vec![from],
|
||||||
.map_err(err_str)?;
|
selected: vec![to],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(err_str)??
|
||||||
}
|
}
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
Self::Restore { .. } => {
|
Self::Restore { .. } => {
|
||||||
|
|
@ -1024,9 +1055,10 @@ impl Operation {
|
||||||
return Err("Restoring from trash is not supported on macos".to_string());
|
return Err("Restoring from trash is not supported on macos".to_string());
|
||||||
}
|
}
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
Self::Restore { paths } => {
|
Self::Restore { items } => {
|
||||||
let total = paths.len();
|
let total = items.len();
|
||||||
for (i, path) in paths.into_iter().enumerate() {
|
let mut paths = Vec::with_capacity(total);
|
||||||
|
for (i, item) in items.into_iter().enumerate() {
|
||||||
controller.check()?;
|
controller.check()?;
|
||||||
|
|
||||||
let _ = msg_tx
|
let _ = msg_tx
|
||||||
|
|
@ -1038,11 +1070,17 @@ impl Operation {
|
||||||
))
|
))
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
tokio::task::spawn_blocking(|| trash::os_limited::restore_all([path]))
|
paths.push(item.original_path());
|
||||||
|
|
||||||
|
tokio::task::spawn_blocking(|| trash::os_limited::restore_all([item]))
|
||||||
.await
|
.await
|
||||||
.map_err(err_str)?
|
.map_err(err_str)?
|
||||||
.map_err(err_str)?;
|
.map_err(err_str)?;
|
||||||
}
|
}
|
||||||
|
OperationSelection {
|
||||||
|
ignored: Vec::new(),
|
||||||
|
selected: paths,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Self::SetExecutableAndLaunch { path } => {
|
Self::SetExecutableAndLaunch { path } => {
|
||||||
tokio::task::spawn_blocking(move || -> Result<(), String> {
|
tokio::task::spawn_blocking(move || -> Result<(), String> {
|
||||||
|
|
@ -1070,8 +1108,9 @@ impl Operation {
|
||||||
.await
|
.await
|
||||||
.map_err(err_str)?
|
.map_err(err_str)?
|
||||||
.map_err(err_str)?;
|
.map_err(err_str)?;
|
||||||
|
OperationSelection::default()
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
let _ = msg_tx
|
let _ = msg_tx
|
||||||
.lock()
|
.lock()
|
||||||
|
|
@ -1079,7 +1118,7 @@ impl Operation {
|
||||||
.send(Message::PendingProgress(id, 100.0))
|
.send(Message::PendingProgress(id, 100.0))
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
Ok(())
|
Ok(paths)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1112,7 +1151,10 @@ mod tests {
|
||||||
const BUF_SIZE: usize = 8;
|
const BUF_SIZE: usize = 8;
|
||||||
|
|
||||||
/// Simple wrapper around `[Operation::Copy]`
|
/// Simple wrapper around `[Operation::Copy]`
|
||||||
pub async fn operation_copy(paths: Vec<PathBuf>, to: PathBuf) -> Result<(), String> {
|
pub async fn operation_copy(
|
||||||
|
paths: Vec<PathBuf>,
|
||||||
|
to: PathBuf,
|
||||||
|
) -> Result<OperationSelection, String> {
|
||||||
let id = fastrand::u64(0..u64::MAX);
|
let id = fastrand::u64(0..u64::MAX);
|
||||||
let (tx, mut rx) = mpsc::channel(BUF_SIZE);
|
let (tx, mut rx) = mpsc::channel(BUF_SIZE);
|
||||||
let paths_clone = paths.clone();
|
let paths_clone = paths.clone();
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,14 @@ use std::{
|
||||||
};
|
};
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
use super::{copy_unique_path, Controller, ReplaceResult};
|
use super::{copy_unique_path, Controller, OperationSelection, ReplaceResult};
|
||||||
|
|
||||||
pub struct Context {
|
pub struct Context {
|
||||||
buf: Vec<u8>,
|
buf: Vec<u8>,
|
||||||
controller: Controller,
|
controller: Controller,
|
||||||
on_progress: Box<dyn Fn(&Op, &Progress) + 'static>,
|
on_progress: Box<dyn Fn(&Op, &Progress) + 'static>,
|
||||||
on_replace: Box<dyn Fn(&Op) -> ReplaceResult + 'static>,
|
on_replace: Box<dyn Fn(&Op) -> ReplaceResult + 'static>,
|
||||||
|
pub(crate) op_sel: OperationSelection,
|
||||||
replace_result_opt: Option<ReplaceResult>,
|
replace_result_opt: Option<ReplaceResult>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -24,6 +25,7 @@ impl Context {
|
||||||
controller,
|
controller,
|
||||||
on_progress: Box::new(|_op, _progress| {}),
|
on_progress: Box::new(|_op, _progress| {}),
|
||||||
on_replace: Box::new(|_op| ReplaceResult::Cancel),
|
on_replace: Box::new(|_op| ReplaceResult::Cancel),
|
||||||
|
op_sel: OperationSelection::default(),
|
||||||
replace_result_opt: None,
|
replace_result_opt: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -88,6 +90,8 @@ impl Context {
|
||||||
}
|
}
|
||||||
ops.push(op);
|
ops.push(op);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.op_sel.ignored.push(from_parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add cleanup ops after standard ops, in reverse
|
// Add cleanup ops after standard ops, in reverse
|
||||||
|
|
@ -106,12 +110,19 @@ impl Context {
|
||||||
total_bytes: None,
|
total_bytes: None,
|
||||||
};
|
};
|
||||||
(self.on_progress)(&op, &progress);
|
(self.on_progress)(&op, &progress);
|
||||||
if !op.run(self, progress).map_err(|err| {
|
if op.run(self, progress).map_err(|err| {
|
||||||
format!(
|
format!(
|
||||||
"failed to {:?} {:?} to {:?}: {}",
|
"failed to {:?} {:?} to {:?}: {}",
|
||||||
op.kind, op.from, op.to, err
|
op.kind, op.from, op.to, err
|
||||||
)
|
)
|
||||||
})? {
|
})? {
|
||||||
|
// The from path is ignored in the operation selection if it is a top level item
|
||||||
|
if self.op_sel.ignored.contains(&op.from) {
|
||||||
|
// So add the to path to the selection
|
||||||
|
self.op_sel.selected.push(op.to.clone());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Cancelled
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
28
src/tab.rs
28
src/tab.rs
|
|
@ -43,7 +43,7 @@ use mime_guess::{mime, Mime};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
cell::{Cell, RefCell},
|
cell::Cell,
|
||||||
cmp::Ordering,
|
cmp::Ordering,
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
fmt::{self, Display},
|
fmt::{self, Display},
|
||||||
|
|
@ -1015,7 +1015,7 @@ pub enum Command {
|
||||||
Action(Action),
|
Action(Action),
|
||||||
AddNetworkDrive,
|
AddNetworkDrive,
|
||||||
AddToSidebar(PathBuf),
|
AddToSidebar(PathBuf),
|
||||||
ChangeLocation(String, Location, Option<PathBuf>),
|
ChangeLocation(String, Location, Option<Vec<PathBuf>>),
|
||||||
DropFiles(PathBuf, ClipboardPaste),
|
DropFiles(PathBuf, ClipboardPaste),
|
||||||
EmptyTrash,
|
EmptyTrash,
|
||||||
#[cfg(feature = "desktop")]
|
#[cfg(feature = "desktop")]
|
||||||
|
|
@ -1663,7 +1663,6 @@ pub struct Tab {
|
||||||
scrollable_id: widget::Id,
|
scrollable_id: widget::Id,
|
||||||
select_focus: Option<usize>,
|
select_focus: Option<usize>,
|
||||||
select_range: Option<(usize, usize)>,
|
select_range: Option<(usize, usize)>,
|
||||||
cached_selected: RefCell<Option<bool>>,
|
|
||||||
clicked: Option<usize>,
|
clicked: Option<usize>,
|
||||||
selected_clicked: bool,
|
selected_clicked: bool,
|
||||||
last_right_click: Option<usize>,
|
last_right_click: Option<usize>,
|
||||||
|
|
@ -1751,7 +1750,6 @@ impl Tab {
|
||||||
scrollable_id: widget::Id::unique(),
|
scrollable_id: widget::Id::unique(),
|
||||||
select_focus: None,
|
select_focus: None,
|
||||||
select_range: None,
|
select_range: None,
|
||||||
cached_selected: RefCell::new(None),
|
|
||||||
clicked: None,
|
clicked: None,
|
||||||
dnd_hovered: None,
|
dnd_hovered: None,
|
||||||
selected_clicked: false,
|
selected_clicked: false,
|
||||||
|
|
@ -1821,7 +1819,6 @@ impl Tab {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select_all(&mut self) {
|
pub fn select_all(&mut self) {
|
||||||
*self.cached_selected.borrow_mut() = None;
|
|
||||||
if let Some(ref mut items) = self.items_opt {
|
if let Some(ref mut items) = self.items_opt {
|
||||||
for item in items.iter_mut() {
|
for item in items.iter_mut() {
|
||||||
if !self.config.show_hidden && item.hidden {
|
if !self.config.show_hidden && item.hidden {
|
||||||
|
|
@ -1834,7 +1831,6 @@ impl Tab {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select_none(&mut self) -> bool {
|
pub fn select_none(&mut self) -> bool {
|
||||||
*self.cached_selected.borrow_mut() = None;
|
|
||||||
self.select_focus = None;
|
self.select_focus = None;
|
||||||
let mut had_selection = false;
|
let mut had_selection = false;
|
||||||
if let Some(ref mut items) = self.items_opt {
|
if let Some(ref mut items) = self.items_opt {
|
||||||
|
|
@ -1849,7 +1845,6 @@ impl Tab {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select_name(&mut self, name: &str) {
|
pub fn select_name(&mut self, name: &str) {
|
||||||
*self.cached_selected.borrow_mut() = None;
|
|
||||||
if let Some(ref mut items) = self.items_opt {
|
if let Some(ref mut items) = self.items_opt {
|
||||||
for item in items.iter_mut() {
|
for item in items.iter_mut() {
|
||||||
item.selected = item.name == name;
|
item.selected = item.name == name;
|
||||||
|
|
@ -1857,18 +1852,20 @@ impl Tab {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select_path(&mut self, path: PathBuf) {
|
pub fn select_paths(&mut self, paths: Vec<PathBuf>) {
|
||||||
let location = Location::Path(path);
|
|
||||||
*self.cached_selected.borrow_mut() = None;
|
|
||||||
if let Some(ref mut items) = self.items_opt {
|
if let Some(ref mut items) = self.items_opt {
|
||||||
for item in items.iter_mut() {
|
for item in items.iter_mut() {
|
||||||
item.selected = item.location_opt.as_ref() == Some(&location);
|
item.selected = false;
|
||||||
|
if let Some(path) = item.path_opt() {
|
||||||
|
if paths.contains(path) {
|
||||||
|
item.selected = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_position(&mut self, row: usize, col: usize, mod_shift: bool) -> bool {
|
fn select_position(&mut self, row: usize, col: usize, mod_shift: bool) -> bool {
|
||||||
*self.cached_selected.borrow_mut() = None;
|
|
||||||
let mut start = (row, col);
|
let mut start = (row, col);
|
||||||
let mut end = (row, col);
|
let mut end = (row, col);
|
||||||
if mod_shift {
|
if mod_shift {
|
||||||
|
|
@ -1919,7 +1916,6 @@ impl Tab {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select_rect(&mut self, rect: Rectangle, mod_ctrl: bool, mod_shift: bool) {
|
pub fn select_rect(&mut self, rect: Rectangle, mod_ctrl: bool, mod_shift: bool) {
|
||||||
*self.cached_selected.borrow_mut() = None;
|
|
||||||
if let Some(ref mut items) = self.items_opt {
|
if let Some(ref mut items) = self.items_opt {
|
||||||
for item in items.iter_mut() {
|
for item in items.iter_mut() {
|
||||||
let was_overlapped = item.overlaps_drag_rect;
|
let was_overlapped = item.overlaps_drag_rect;
|
||||||
|
|
@ -1996,7 +1992,6 @@ impl Tab {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_first_pos_opt(&self) -> Option<(usize, usize)> {
|
fn select_first_pos_opt(&self) -> Option<(usize, usize)> {
|
||||||
*self.cached_selected.borrow_mut() = None;
|
|
||||||
let items = self.items_opt.as_ref()?;
|
let items = self.items_opt.as_ref()?;
|
||||||
let mut first = None;
|
let mut first = None;
|
||||||
for item in items.iter() {
|
for item in items.iter() {
|
||||||
|
|
@ -2026,7 +2021,6 @@ impl Tab {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_last_pos_opt(&self) -> Option<(usize, usize)> {
|
fn select_last_pos_opt(&self) -> Option<(usize, usize)> {
|
||||||
*self.cached_selected.borrow_mut() = None;
|
|
||||||
let items = self.items_opt.as_ref()?;
|
let items = self.items_opt.as_ref()?;
|
||||||
let mut last = None;
|
let mut last = None;
|
||||||
for item in items.iter() {
|
for item in items.iter() {
|
||||||
|
|
@ -2231,7 +2225,6 @@ impl Tab {
|
||||||
l.iter()
|
l.iter()
|
||||||
.any(|(e_i, e)| Some(e_i) == click_i_opt.as_ref() && e.selected)
|
.any(|(e_i, e)| Some(e_i) == click_i_opt.as_ref() && e.selected)
|
||||||
});
|
});
|
||||||
*self.cached_selected.borrow_mut() = None;
|
|
||||||
if let Some(ref mut items) = self.items_opt {
|
if let Some(ref mut items) = self.items_opt {
|
||||||
for (i, item) in items.iter_mut().enumerate() {
|
for (i, item) in items.iter_mut().enumerate() {
|
||||||
if Some(i) == click_i_opt {
|
if Some(i) == click_i_opt {
|
||||||
|
|
@ -2680,7 +2673,6 @@ impl Tab {
|
||||||
}
|
}
|
||||||
Message::RightClick(click_i_opt) => {
|
Message::RightClick(click_i_opt) => {
|
||||||
self.update(Message::Click(click_i_opt), modifiers);
|
self.update(Message::Click(click_i_opt), modifiers);
|
||||||
*self.cached_selected.borrow_mut() = None;
|
|
||||||
if let Some(ref mut items) = self.items_opt {
|
if let Some(ref mut items) = self.items_opt {
|
||||||
if !click_i_opt.map_or(false, |click_i| {
|
if !click_i_opt.map_or(false, |click_i| {
|
||||||
items.get(click_i).map_or(false, |x| x.selected)
|
items.get(click_i).map_or(false, |x| x.selected)
|
||||||
|
|
@ -2978,7 +2970,7 @@ impl Tab {
|
||||||
} else if location != self.location {
|
} else if location != self.location {
|
||||||
if location.path_opt().map_or(true, |path| path.is_dir()) {
|
if location.path_opt().map_or(true, |path| path.is_dir()) {
|
||||||
let prev_path = if let Some(path) = self.location.path_opt() {
|
let prev_path = if let Some(path) = self.location.path_opt() {
|
||||||
Some(path.to_path_buf())
|
Some(vec![path.to_path_buf()])
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue