fix merge conflicts

This commit is contained in:
Francesco Pio Gaglione 2024-08-20 08:30:41 +02:00
commit 1ffb3b9870
9 changed files with 780 additions and 324 deletions

800
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -33,6 +33,7 @@ rayon = "1"
regex = "1" regex = "1"
serde = { version = "1", features = ["serde_derive"] } serde = { version = "1", features = ["serde_derive"] }
shlex = { version = "1.3" } shlex = { version = "1.3" }
tar = "0.4.41"
tokio = { version = "1", features = ["sync"] } tokio = { version = "1", features = ["sync"] }
trash = { git = "https://github.com/jackpot51/trash-rs.git", branch = "delete-info" } trash = { git = "https://github.com/jackpot51/trash-rs.git", branch = "delete-info" }
xdg = { version = "2.5.2", optional = true } xdg = { version = "2.5.2", optional = true }
@ -47,6 +48,7 @@ i18n-embed-fl = "0.7"
rust-embed = "8" rust-embed = "8"
slotmap = "1.0.7" slotmap = "1.0.7"
recently-used-xbel = "1.0.0" recently-used-xbel = "1.0.0"
zip = "2.1.6"
[dependencies.libcosmic] [dependencies.libcosmic]
git = "https://github.com/pop-os/libcosmic.git" git = "https://github.com/pop-os/libcosmic.git"

View file

@ -38,6 +38,7 @@ open-file = Open file
open-folder = Open folder open-folder = Open folder
open-in-new-tab = Open in new tab open-in-new-tab = Open in new tab
open-in-new-window = Open in new window open-in-new-window = Open in new window
open-item-location = Open item location
open-multiple-files = Open multiple files open-multiple-files = Open multiple files
open-multiple-folders = Open multiple folders open-multiple-folders = Open multiple folders
save = Save save = Save
@ -83,6 +84,14 @@ copied = Copied {$items} {$items ->
} from {$from} to {$to} } from {$from} to {$to}
emptying-trash = Emptying {trash} emptying-trash = Emptying {trash}
emptied-trash = Emptied {trash} emptied-trash = Emptied {trash}
extracting = Extracting {$items} {$items ->
[one] item
*[other] items
} from {$from} to {$to}
extracted = Extracted {$items} {$items ->
[one] item
*[other] items
} from {$from} to {$to}
moving = Moving {$items} {$items -> moving = Moving {$items} {$items ->
[one] item [one] item
*[other] items *[other] items
@ -131,6 +140,7 @@ dark = Dark
light = Light light = Light
# Context menu # Context menu
extract-here = Extract
add-to-sidebar = Add to sidebar add-to-sidebar = Add to sidebar
new-file = New file... new-file = New file...
new-folder = New folder... new-folder = New folder...

View file

@ -71,6 +71,7 @@ pub enum Action {
Cut, Cut,
EditHistory, EditHistory,
EditLocation, EditLocation,
ExtractHere,
HistoryNext, HistoryNext,
HistoryPrevious, HistoryPrevious,
ItemDown, ItemDown,
@ -84,6 +85,7 @@ pub enum Action {
Open, Open,
OpenInNewTab, OpenInNewTab,
OpenInNewWindow, OpenInNewWindow,
OpenItemLocation,
OpenTerminal, OpenTerminal,
OpenWith, OpenWith,
Paste, Paste,
@ -119,6 +121,7 @@ impl Action {
Action::Cut => Message::Cut(entity_opt), Action::Cut => Message::Cut(entity_opt),
Action::EditHistory => Message::ToggleContextPage(ContextPage::EditHistory), Action::EditHistory => Message::ToggleContextPage(ContextPage::EditHistory),
Action::EditLocation => Message::EditLocation(entity_opt), Action::EditLocation => Message::EditLocation(entity_opt),
Action::ExtractHere => Message::ExtractHere(entity_opt),
Action::HistoryNext => Message::TabMessage(entity_opt, tab::Message::GoNext), Action::HistoryNext => Message::TabMessage(entity_opt, tab::Message::GoNext),
Action::HistoryPrevious => Message::TabMessage(entity_opt, tab::Message::GoPrevious), Action::HistoryPrevious => Message::TabMessage(entity_opt, tab::Message::GoPrevious),
Action::ItemDown => Message::TabMessage(entity_opt, tab::Message::ItemDown), Action::ItemDown => Message::TabMessage(entity_opt, tab::Message::ItemDown),
@ -132,6 +135,7 @@ impl Action {
Action::Open => Message::TabMessage(entity_opt, tab::Message::Open), Action::Open => Message::TabMessage(entity_opt, tab::Message::Open),
Action::OpenInNewTab => Message::OpenInNewTab(entity_opt), Action::OpenInNewTab => Message::OpenInNewTab(entity_opt),
Action::OpenInNewWindow => Message::OpenInNewWindow(entity_opt), Action::OpenInNewWindow => Message::OpenInNewWindow(entity_opt),
Action::OpenItemLocation => Message::OpenItemLocation(entity_opt),
Action::OpenTerminal => Message::OpenTerminal(entity_opt), Action::OpenTerminal => Message::OpenTerminal(entity_opt),
Action::OpenWith => Message::ToggleContextPage(ContextPage::OpenWith), Action::OpenWith => Message::ToggleContextPage(ContextPage::OpenWith),
Action::Paste => Message::Paste(entity_opt), Action::Paste => Message::Paste(entity_opt),
@ -216,6 +220,7 @@ pub enum Message {
DialogPush(DialogPage), DialogPush(DialogPage),
DialogUpdate(DialogPage), DialogUpdate(DialogPage),
EditLocation(Option<Entity>), EditLocation(Option<Entity>),
ExtractHere(Option<Entity>),
Key(Modifiers, Key), Key(Modifiers, Key),
LaunchUrl(String), LaunchUrl(String),
MaybeExit, MaybeExit,
@ -233,6 +238,7 @@ pub enum Message {
OpenWith(PathBuf, mime_app::MimeApp), OpenWith(PathBuf, mime_app::MimeApp),
OpenInNewTab(Option<Entity>), OpenInNewTab(Option<Entity>),
OpenInNewWindow(Option<Entity>), OpenInNewWindow(Option<Entity>),
OpenItemLocation(Option<Entity>),
Paste(Option<Entity>), Paste(Option<Entity>),
PasteContents(PathBuf, ClipboardPaste), PasteContents(PathBuf, ClipboardPaste),
PendingComplete(u64), PendingComplete(u64),
@ -254,7 +260,7 @@ pub enum Message {
TabConfig(TabConfig), TabConfig(TabConfig),
TabMessage(Option<Entity>, tab::Message), TabMessage(Option<Entity>, tab::Message),
TabNew, TabNew,
TabRescan(Entity, Location, Vec<tab::Item>), TabRescan(Entity, Location, Vec<tab::Item>, Option<PathBuf>),
ToggleContextPage(ContextPage), ToggleContextPage(ContextPage),
Undo(usize), Undo(usize),
UndoTrash(usize, Arc<[PathBuf]>), UndoTrash(usize, Arc<[PathBuf]>),
@ -380,23 +386,30 @@ pub struct App {
} }
impl App { impl App {
fn open_tab(&mut self, location: Location) -> Command<Message> { fn open_tab(
&mut self,
location: Location,
activate: bool,
selection_path: Option<PathBuf>,
) -> Command<Message> {
let tab = Tab::new(location.clone(), self.config.tab); let tab = Tab::new(location.clone(), self.config.tab);
let entity = self let entity = self
.tab_model .tab_model
.insert() .insert()
.text(tab.title()) .text(tab.title())
.data(tab) .data(tab)
.closable() .closable();
.activate()
.id();
self.activate_nav_model_location(&location); let entity = if activate {
entity.activate().id()
} else {
entity.id()
};
Command::batch([ Command::batch([
self.update_title(), self.update_title(),
self.update_watcher(), self.update_watcher(),
self.rescan_tab(entity, location), self.rescan_tab(entity, location, selection_path),
]) ])
} }
@ -406,13 +419,20 @@ impl App {
self.pending_operations.insert(id, (operation, 0.0)); self.pending_operations.insert(id, (operation, 0.0));
} }
fn rescan_tab(&mut self, entity: Entity, location: Location) -> Command<Message> { fn rescan_tab(
&mut self,
entity: Entity,
location: Location,
selection_path: Option<PathBuf>,
) -> Command<Message> {
let icon_sizes = self.config.tab.icon_sizes; let icon_sizes = self.config.tab.icon_sizes;
Command::perform( Command::perform(
async move { async move {
let location2 = location.clone(); let location2 = location.clone();
match tokio::task::spawn_blocking(move || location2.scan(icon_sizes)).await { match tokio::task::spawn_blocking(move || location2.scan(icon_sizes)).await {
Ok(items) => message::app(Message::TabRescan(entity, location, items)), Ok(items) => {
message::app(Message::TabRescan(entity, location, items, selection_path))
}
Err(err) => { Err(err) => {
log::warn!("failed to rescan: {}", err); log::warn!("failed to rescan: {}", err);
message::none() message::none()
@ -435,7 +455,7 @@ impl App {
let mut commands = Vec::with_capacity(needs_reload.len()); let mut commands = Vec::with_capacity(needs_reload.len());
for (entity, location) in needs_reload { for (entity, location) in needs_reload {
commands.push(self.rescan_tab(entity, location)); commands.push(self.rescan_tab(entity, location, None));
} }
Command::batch(commands) Command::batch(commands)
} }
@ -448,14 +468,10 @@ impl App {
Location::Path(path) | Location::Search(path, ..) => { Location::Path(path) | Location::Search(path, ..) => {
let location = if !self.search_input.is_empty() { let location = if !self.search_input.is_empty() {
Location::Search(path.clone(), self.search_input.clone()) Location::Search(path.clone(), self.search_input.clone())
} } else {
else {
Location::Path(path.clone()) Location::Path(path.clone())
}; };
tab.change_location( tab.change_location(&location, None);
&location,
None,
);
title_location_opt = Some((tab.title(), tab.location.clone())); title_location_opt = Some((tab.title(), tab.location.clone()));
} }
_ => {} _ => {}
@ -466,7 +482,7 @@ impl App {
return Command::batch([ return Command::batch([
self.update_title(), self.update_title(),
self.update_watcher(), self.update_watcher(),
self.rescan_tab(entity, location), self.rescan_tab(entity, location, None),
]); ]);
} }
Command::none() Command::none()
@ -1082,14 +1098,14 @@ impl Application for App {
} }
} }
}; };
commands.push(app.open_tab(location)); commands.push(app.open_tab(location, true, None));
} }
if app.tab_model.iter().next().is_none() { if app.tab_model.iter().next().is_none() {
if let Ok(current_dir) = env::current_dir() { if let Ok(current_dir) = env::current_dir() {
commands.push(app.open_tab(Location::Path(current_dir))); commands.push(app.open_tab(Location::Path(current_dir), true, None));
} else { } else {
commands.push(app.open_tab(Location::Path(home_dir()))); commands.push(app.open_tab(Location::Path(home_dir()), true, None));
} }
} }
@ -1330,6 +1346,18 @@ impl Application for App {
)); ));
} }
} }
Message::ExtractHere(entity_opt) => {
let paths = self.selected_paths(entity_opt);
if let Some(current_path) = paths.get(0) {
if let Some(destination) = current_path.parent().zip(current_path.file_stem()) {
let destination_path = destination.0.to_path_buf();
self.operation(Operation::Extract {
paths,
to: destination_path,
});
}
}
}
Message::Key(modifiers, key) => { Message::Key(modifiers, key) => {
let entity = self.tab_model.active(); let entity = self.tab_model.active();
for (key_bind, action) in self.key_binds.iter() { for (key_bind, action) in self.key_binds.iter() {
@ -1404,7 +1432,7 @@ impl Application for App {
}; };
if let Some(title) = title_opt { if let Some(title) = title_opt {
self.tab_model.text_set(entity, title); self.tab_model.text_set(entity, title);
commands.push(self.rescan_tab(entity, home_location.clone())); commands.push(self.rescan_tab(entity, home_location.clone(), None));
} }
} }
if !commands.is_empty() { if !commands.is_empty() {
@ -1502,7 +1530,7 @@ impl Application for App {
let mut commands = Vec::with_capacity(needs_reload.len()); let mut commands = Vec::with_capacity(needs_reload.len());
for (entity, location) in needs_reload { for (entity, location) in needs_reload {
commands.push(self.rescan_tab(entity, location)); commands.push(self.rescan_tab(entity, location, None));
} }
return Command::batch(commands); return Command::batch(commands);
} }
@ -1575,7 +1603,7 @@ impl Application for App {
return Command::batch(self.selected_paths(entity_opt).into_iter().filter_map( return Command::batch(self.selected_paths(entity_opt).into_iter().filter_map(
|path| { |path| {
if path.is_dir() { if path.is_dir() {
Some(self.open_tab(Location::Path(path))) Some(self.open_tab(Location::Path(path), false, None))
} else { } else {
None None
} }
@ -1597,6 +1625,21 @@ impl Application for App {
log::error!("failed to get current executable path: {}", err); log::error!("failed to get current executable path: {}", err);
} }
}, },
Message::OpenItemLocation(entity_opt) => {
return Command::batch(self.selected_paths(entity_opt).into_iter().filter_map(
|path| {
if let Some(parent) = path.parent() {
Some(self.open_tab(
Location::Path(parent.to_path_buf()),
true,
Some(path),
))
} else {
None
}
},
))
}
Message::Paste(entity_opt) => { Message::Paste(entity_opt) => {
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) {
@ -1909,14 +1952,14 @@ impl Application for App {
tab::Command::Action(action) => { tab::Command::Action(action) => {
commands.push(self.update(action.message(Some(entity)))); commands.push(self.update(action.message(Some(entity))));
} }
tab::Command::ChangeLocation(tab_title, tab_path) => { tab::Command::ChangeLocation(tab_title, tab_path, selection_path) => {
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(Command::batch([ commands.push(Command::batch([
self.update_title(), self.update_title(),
self.update_watcher(), self.update_watcher(),
self.rescan_tab(entity, tab_path), self.rescan_tab(entity, tab_path, selection_path),
])); ]));
} }
tab::Command::EmptyTrash => { tab::Command::EmptyTrash => {
@ -1937,7 +1980,7 @@ impl Application for App {
} }
} }
tab::Command::OpenInNewTab(path) => { tab::Command::OpenInNewTab(path) => {
commands.push(self.open_tab(Location::Path(path.clone()))); commands.push(self.open_tab(Location::Path(path.clone()), false, None));
} }
tab::Command::OpenInNewWindow(path) => match env::current_exe() { tab::Command::OpenInNewWindow(path) => match env::current_exe() {
Ok(exe) => match process::Command::new(&exe).arg(path).spawn() { Ok(exe) => match process::Command::new(&exe).arg(path).spawn() {
@ -1989,13 +2032,16 @@ impl Application for App {
Some(tab) => tab.location.clone(), Some(tab) => tab.location.clone(),
None => Location::Path(home_dir()), None => Location::Path(home_dir()),
}; };
return self.open_tab(location); return self.open_tab(location, true, None);
} }
Message::TabRescan(entity, location, items) => { Message::TabRescan(entity, location, items, selection_path) => {
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.set_items(items); tab.set_items(items);
if let Some(selection_path) = selection_path {
tab.select_path(selection_path);
}
} }
} }
_ => (), _ => (),
@ -2125,7 +2171,7 @@ impl Application for App {
return Command::batch([ return Command::batch([
self.update_title(), self.update_title(),
self.update_watcher(), self.update_watcher(),
self.rescan_tab(entity, location), self.rescan_tab(entity, location, None),
]); ]);
} }
} }
@ -2195,10 +2241,10 @@ impl Application for App {
NavMenuAction::OpenInNewTab(entity) => { NavMenuAction::OpenInNewTab(entity) => {
match self.nav_model.data::<Location>(entity) { match self.nav_model.data::<Location>(entity) {
Some(Location::Path(ref path)) => { Some(Location::Path(ref path)) => {
return self.open_tab(Location::Path(path.clone())); return self.open_tab(Location::Path(path.clone()), false, None);
} }
Some(Location::Trash) => { Some(Location::Trash) => {
return self.open_tab(Location::Trash); return self.open_tab(Location::Trash, false, None);
} }
_ => {} _ => {}
} }
@ -2244,7 +2290,7 @@ impl Application for App {
} }
}, },
Message::Recents => { Message::Recents => {
return self.open_tab(Location::Recents); return self.open_tab(Location::Recents, false, None);
} }
} }

View file

@ -754,7 +754,7 @@ impl Application for App {
tab::Command::Action(action) => { tab::Command::Action(action) => {
log::warn!("Action {:?} not supported in dialog", action); log::warn!("Action {:?} not supported in dialog", action);
} }
tab::Command::ChangeLocation(_tab_title, _tab_path) => { tab::Command::ChangeLocation(_tab_title, _tab_path, _selection_path) => {
commands commands
.push(Command::batch([self.update_watcher(), self.rescan_tab()])); .push(Command::batch([self.update_watcher(), self.rescan_tab()]));
} }

View file

@ -9,6 +9,7 @@ use cosmic::{
widget::menu::{self, key_bind::KeyBind, ItemHeight, ItemWidth, MenuBar}, widget::menu::{self, key_bind::KeyBind, ItemHeight, ItemWidth, MenuBar},
Element, Element,
}; };
use mime_guess::Mime;
use std::collections::HashMap; use std::collections::HashMap;
use crate::{ use crate::{
@ -79,6 +80,7 @@ pub fn context_menu<'a>(
let mut selected_dir = 0; let mut selected_dir = 0;
let mut selected = 0; let mut selected = 0;
let mut selected_types: Vec<Mime> = vec![];
tab.items_opt().map(|items| { tab.items_opt().map(|items| {
for item in items.iter() { for item in items.iter() {
if item.selected { if item.selected {
@ -86,9 +88,12 @@ pub fn context_menu<'a>(
if item.metadata.is_dir() { if item.metadata.is_dir() {
selected_dir += 1; selected_dir += 1;
} }
selected_types.push(item.mime.clone());
} }
} }
}); });
selected_types.sort_unstable();
selected_types.dedup();
let mut children: Vec<Element<_>> = Vec::new(); let mut children: Vec<Element<_>> = Vec::new();
match tab.location { match tab.location {
@ -104,6 +109,11 @@ pub fn context_menu<'a>(
.push(menu_item(fl!("open-in-terminal"), Action::OpenTerminal).into()); .push(menu_item(fl!("open-in-terminal"), Action::OpenTerminal).into());
} }
} }
if matches!(tab.location, Location::Search(_, _)) {
children.push(
menu_item(fl!("open-item-location"), Action::OpenItemLocation).into(),
);
}
// All selected items are directories // All selected items are directories
if selected == selected_dir { if selected == selected_dir {
children.push(menu_item(fl!("open-in-new-tab"), Action::OpenInNewTab).into()); children.push(menu_item(fl!("open-in-new-tab"), Action::OpenInNewTab).into());
@ -114,6 +124,16 @@ pub fn context_menu<'a>(
children.push(menu_item(fl!("rename"), Action::Rename).into()); children.push(menu_item(fl!("rename"), Action::Rename).into());
children.push(menu_item(fl!("cut"), Action::Cut).into()); children.push(menu_item(fl!("cut"), Action::Cut).into());
children.push(menu_item(fl!("copy"), Action::Copy).into()); children.push(menu_item(fl!("copy"), Action::Copy).into());
let supported_archive_types = ["application/x-tar", "application/zip"]
.iter()
.filter_map(|mime_type| mime_type.parse::<Mime>().ok())
.collect::<Vec<_>>();
selected_types.retain(|t| !supported_archive_types.contains(t));
if selected_types.is_empty() {
children.push(menu_item(fl!("extract-here"), Action::ExtractHere).into());
}
//TODO: Print? //TODO: Print?
children.push(container(horizontal_rule(1)).padding([0, 8]).into()); children.push(container(horizontal_rule(1)).padding([0, 8]).into());
children.push(menu_item(fl!("show-details"), Action::Properties).into()); children.push(menu_item(fl!("show-details"), Action::Properties).into());

View file

@ -1,8 +1,11 @@
use cosmic::iced::futures::{channel::mpsc::Sender, executor, SinkExt}; use cosmic::iced::futures::{channel::mpsc::Sender, executor, SinkExt};
use mime_guess::MimeGuess;
use std::{ use std::{
borrow::Cow, borrow::Cow,
fs, fs,
io::{self, Error},
path::{Path, PathBuf}, path::{Path, PathBuf},
process,
sync::{ sync::{
atomic::{self, AtomicU64}, atomic::{self, AtomicU64},
Arc, Arc,
@ -138,6 +141,11 @@ pub enum Operation {
}, },
/// Empty the trash /// Empty the trash
EmptyTrash, EmptyTrash,
/// Uncompress files
Extract {
paths: Vec<PathBuf>,
to: PathBuf,
},
/// Move items /// Move items
Move { Move {
paths: Vec<PathBuf>, paths: Vec<PathBuf>,
@ -246,6 +254,12 @@ impl Operation {
to = fl!("trash") to = fl!("trash")
), ),
Self::EmptyTrash => fl!("emptying-trash"), Self::EmptyTrash => fl!("emptying-trash"),
Self::Extract { paths, to } => fl!(
"extracting",
items = paths.len(),
from = paths_parent_name(paths),
to = file_name(to)
),
Self::Move { paths, to } => fl!( Self::Move { paths, to } => fl!(
"moving", "moving",
items = paths.len(), items = paths.len(),
@ -284,6 +298,12 @@ impl Operation {
to = fl!("trash") to = fl!("trash")
), ),
Self::EmptyTrash => fl!("emptied-trash"), Self::EmptyTrash => fl!("emptied-trash"),
Self::Extract { paths, to } => fl!(
"extracted",
items = paths.len(),
from = paths_parent_name(paths),
to = file_name(to)
),
Self::Move { paths, to } => fl!( Self::Move { paths, to } => fl!(
"moved", "moved",
items = paths.len(), items = paths.len(),
@ -473,6 +493,50 @@ impl Operation {
.send(Message::PendingProgress(id, 100.0)) .send(Message::PendingProgress(id, 100.0))
.await; .await;
} }
Self::Extract { paths, to } => {
for path in paths {
let to = to.to_owned();
tokio::task::spawn_blocking(move || -> Result<(), String> {
if let Some(file_stem) = path.file_stem() {
let mut new_dir = to.join(file_stem);
if new_dir.exists() {
let mut extensionless_path = path.to_owned();
extensionless_path.set_extension("");
if let Some(new_dir_parent) = new_dir.parent() {
new_dir = copy_unique_path(&extensionless_path, new_dir_parent);
}
}
if let Some(mime) = mime_guess::from_path(&path).first() {
match mime.essence_str() {
"application/x-tar" => {
return fs::File::open(path)
.map(io::BufReader::new)
.map(tar::Archive::new)
.and_then(|mut archive| archive.unpack(new_dir))
.map_err(err_str)
}
"application/zip" => {
return fs::File::open(path)
.map(io::BufReader::new)
.map(zip::ZipArchive::new)
.map_err(err_str)?
.and_then(|mut archive| archive.extract(new_dir))
.map_err(err_str)
}
_ => Err(format!("unsupported mime type {:?}", mime))?,
}
}
}
Ok(())
})
.await
.map_err(err_str)?
.map_err(err_str)?;
}
}
Self::Move { paths, to } => { Self::Move { paths, to } => {
let msg_tx = msg_tx.clone(); let msg_tx = msg_tx.clone();
tokio::task::spawn_blocking(move || { tokio::task::spawn_blocking(move || {

View file

@ -336,7 +336,7 @@ pub fn scan_path(tab_path: &PathBuf, sizes: IconSizes) -> Vec<Item> {
} }
}; };
let metadata = match entry.metadata() { let metadata = match fs::metadata(&path) {
Ok(ok) => ok, Ok(ok) => ok,
Err(err) => { Err(err) => {
log::warn!("failed to read metadata for entry at {:?}: {}", path, err); log::warn!("failed to read metadata for entry at {:?}: {}", path, err);
@ -397,7 +397,7 @@ pub fn scan_search(tab_path: &PathBuf, term: &str, sizes: IconSizes) -> Vec<Item
if regex.is_match(file_name) { if regex.is_match(file_name) {
let path = entry.path(); let path = entry.path();
let metadata = match entry.metadata() { let metadata = match fs::metadata(&path) {
Ok(ok) => ok, Ok(ok) => ok,
Err(err) => { Err(err) => {
log::warn!("failed to read metadata for entry at {:?}: {}", path, err); log::warn!("failed to read metadata for entry at {:?}: {}", path, err);
@ -652,7 +652,7 @@ impl Location {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Command { pub enum Command {
Action(Action), Action(Action),
ChangeLocation(String, Location), ChangeLocation(String, Location, Option<PathBuf>),
EmptyTrash, EmptyTrash,
FocusButton(widget::Id), FocusButton(widget::Id),
FocusTextInput(widget::Id), FocusTextInput(widget::Id),
@ -1003,6 +1003,7 @@ pub struct Tab {
pub dialog: Option<DialogKind>, pub dialog: Option<DialogKind>,
pub scroll_opt: Option<AbsoluteOffset>, pub scroll_opt: Option<AbsoluteOffset>,
pub size_opt: Cell<Option<Size>>, pub size_opt: Cell<Option<Size>>,
pub item_view_size_opt: Cell<Option<Size>>,
pub edit_location: Option<Location>, pub edit_location: Option<Location>,
pub edit_location_id: widget::Id, pub edit_location_id: widget::Id,
pub history_i: usize, pub history_i: usize,
@ -1029,6 +1030,7 @@ impl Tab {
dialog: None, dialog: None,
scroll_opt: None, scroll_opt: None,
size_opt: Cell::new(None), size_opt: Cell::new(None),
item_view_size_opt: Cell::new(None),
edit_location: None, edit_location: None,
edit_location_id: widget::Id::unique(), edit_location_id: widget::Id::unique(),
history_i: 0, history_i: 0,
@ -1104,11 +1106,16 @@ impl Tab {
*self.cached_selected.borrow_mut() = None; *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 item.name == name { item.selected = item.name == name;
item.selected = true; }
} else { }
item.selected = false; }
}
pub fn select_path(&mut self, path: PathBuf) {
*self.cached_selected.borrow_mut() = None;
if let Some(ref mut items) = self.items_opt {
for item in items.iter_mut() {
item.selected = item.path_opt.as_ref() == Some(&path);
} }
} }
} }
@ -1211,7 +1218,10 @@ impl Tab {
Some(offset) => Point::new(0.0, offset.y), Some(offset) => Point::new(0.0, offset.y),
None => Point::new(0.0, 0.0), None => Point::new(0.0, 0.0),
}; };
let size = self.size_opt.get().unwrap_or_else(|| Size::new(0.0, 0.0)); let size = self
.item_view_size_opt
.get()
.unwrap_or_else(|| Size::new(0.0, 0.0));
Rectangle::new(point, size) Rectangle::new(point, size)
}; };
@ -1949,8 +1959,13 @@ impl Tab {
Location::Trash => true, Location::Trash => true,
Location::Recents => true, Location::Recents => true,
} { } {
let prev_path = if let Location::Path(path) = &self.location {
Some(path.clone())
} else {
None
};
self.change_location(&location, history_i_opt); self.change_location(&location, history_i_opt);
commands.push(Command::ChangeLocation(self.title(), location)); commands.push(Command::ChangeLocation(self.title(), location, prev_path));
} else { } else {
log::warn!("tried to cd to {:?} which is not a directory", location); log::warn!("tried to cd to {:?} which is not a directory", location);
} }
@ -2187,9 +2202,9 @@ impl Tab {
crate::mouse_area::MouseArea::new( crate::mouse_area::MouseArea::new(
widget::button(widget::icon::from_name("edit-symbolic").size(16)) widget::button(widget::icon::from_name("edit-symbolic").size(16))
.padding(space_xxs) .padding(space_xxs)
.style(theme::Button::Icon), .style(theme::Button::Icon)
.on_press(Message::EditLocation(Some(self.location.clone()))),
) )
.on_press(move |_| Message::EditLocation(Some(self.location.clone())))
.on_middle_press(move |_| Message::OpenInNewTab(path.clone())), .on_middle_press(move |_| Message::OpenInNewTab(path.clone())),
); );
w += 16.0 + 2.0 * space_xxs as f32; w += 16.0 + 2.0 * space_xxs as f32;
@ -2289,24 +2304,22 @@ impl Tab {
let mut mouse_area = crate::mouse_area::MouseArea::new( let mut mouse_area = crate::mouse_area::MouseArea::new(
widget::button(row) widget::button(row)
.padding(space_xxxs) .padding(space_xxxs)
.style(theme::Button::Link), .style(theme::Button::Link)
) .on_press(Message::Location(match &self.location {
.on_press(move |_| { Location::Path(_) => Location::Path(ancestor.to_path_buf()),
Message::Location(match &self.location { Location::Search(_, term) => {
Location::Path(_) => Location::Path(ancestor.to_path_buf()), Location::Search(ancestor.to_path_buf(), term.clone())
Location::Search(_, term) => { }
Location::Search(ancestor.to_path_buf(), term.clone()) other => other.clone(),
} })),
other => other.clone(), );
})
});
if self.location_context_menu_index.is_some() { if self.location_context_menu_index.is_some() {
mouse_area = mouse_area.on_right_press(move |_point_opt| { mouse_area = mouse_area.on_right_press(move |_point_opt| {
Message::LocationContextMenuIndex(None) Message::LocationContextMenuIndex(None)
}) })
} else { } else {
mouse_area = mouse_area.on_right_press_no_capture(move |point_opt| { mouse_area = mouse_area.on_right_press_no_capture(move |_point_opt| {
Message::LocationContextMenuIndex(Some(index)) Message::LocationContextMenuIndex(Some(index))
}) })
} }
@ -2619,9 +2632,16 @@ impl Tab {
} }
} }
} }
let spacer_height = height
.checked_sub(max_bottom + 7 * (space_xxs as usize)) let top_deduct = 7 * (space_xxs as usize);
.unwrap_or(0);
self.item_view_size_opt
.set(self.size_opt.get().map(|s| Size {
width: s.width,
height: s.height - top_deduct as f32,
}));
let spacer_height = height.checked_sub(max_bottom + top_deduct).unwrap_or(0);
if spacer_height > 0 { if spacer_height > 0 {
children.push( children.push(
widget::container(vertical_space(Length::Fixed(spacer_height as f32))) widget::container(vertical_space(Length::Fixed(spacer_height as f32)))
@ -2973,12 +2993,18 @@ impl Tab {
} }
//TODO: HACK If we don't reach the bottom of the view, go ahead and add a spacer to do that //TODO: HACK If we don't reach the bottom of the view, go ahead and add a spacer to do that
{ {
let spacer_height = let top_deduct = (if condensed { 6 } else { 9 }) * space_xxs;
size.height as i32 - y as i32 - (if condensed { 6 } else { 9 }) * space_xxs as i32;
if spacer_height > 0 { self.item_view_size_opt
children.push( .set(self.size_opt.get().map(|s| Size {
widget::container(vertical_space(Length::Fixed(spacer_height as f32))).into(), width: s.width,
); height: s.height - top_deduct as f32,
}));
let spacer_height = size.height - y as f32 - top_deduct as f32;
if spacer_height > 0. {
children
.push(widget::container(vertical_space(Length::Fixed(spacer_height))).into());
} }
} }
let drag_col = (!drag_items.is_empty()).then(|| { let drag_col = (!drag_items.is_empty()).then(|| {