Merge pull request #891 from joshuamegnauth54/feat-multiple-uris-single-app
feat: Open multiple files with one/multiple apps
This commit is contained in:
commit
3f03f81ccc
3 changed files with 271 additions and 137 deletions
256
src/app.rs
256
src/app.rs
|
|
@ -66,21 +66,21 @@ use wayland_client::{protocol::wl_output::WlOutput, Proxy};
|
|||
use crate::{
|
||||
clipboard::{ClipboardCopy, ClipboardKind, ClipboardPaste},
|
||||
config::{AppTheme, Config, DesktopConfig, Favorite, IconSizes, TabConfig, TypeToSearch},
|
||||
dialog::{DialogKind, DialogMessage},
|
||||
dialog::{Dialog, DialogKind, DialogMessage, DialogResult},
|
||||
fl, home_dir,
|
||||
key_bind::key_binds,
|
||||
localize::LANGUAGE_SORTER,
|
||||
menu, mime_app, mime_icon,
|
||||
menu,
|
||||
mime_app::{self, MimeApp, MimeAppCache},
|
||||
mime_icon,
|
||||
mounter::{MounterAuth, MounterItem, MounterItems, MounterKey, MounterMessage, MOUNTERS},
|
||||
operation::{Controller, Operation, OperationSelection, ReplaceResult},
|
||||
operation::{
|
||||
Controller, Operation, OperationError, OperationErrorType, OperationSelection,
|
||||
ReplaceResult,
|
||||
},
|
||||
spawn_detached::spawn_detached,
|
||||
tab::{self, HeadingOptions, ItemMetadata, Location, Tab, HOVER_DURATION},
|
||||
};
|
||||
use crate::{
|
||||
dialog::Dialog,
|
||||
operation::{OperationError, OperationErrorType},
|
||||
};
|
||||
use crate::{dialog::DialogResult, mime_app::MimeApp};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Mode {
|
||||
|
|
@ -471,7 +471,7 @@ pub enum DialogPage {
|
|||
path: PathBuf,
|
||||
mime: mime_guess::Mime,
|
||||
selected: usize,
|
||||
store_opt: Option<mime_app::MimeApp>,
|
||||
store_opt: Option<MimeApp>,
|
||||
},
|
||||
RenameItem {
|
||||
from: PathBuf,
|
||||
|
|
@ -544,7 +544,7 @@ pub struct App {
|
|||
dialog_text_input: widget::Id,
|
||||
key_binds: HashMap<KeyBind, Action>,
|
||||
margin: HashMap<window::Id, (f32, f32, f32, f32)>,
|
||||
mime_app_cache: mime_app::MimeAppCache,
|
||||
mime_app_cache: MimeAppCache,
|
||||
modifiers: Modifiers,
|
||||
mounter_items: HashMap<MounterKey, MounterItems>,
|
||||
network_drive_connecting: Option<(MounterKey, String)>,
|
||||
|
|
@ -576,22 +576,95 @@ pub struct App {
|
|||
}
|
||||
|
||||
impl App {
|
||||
fn open_file(&mut self, path: &PathBuf) {
|
||||
let mime = mime_icon::mime_for_path(path);
|
||||
fn open_file(&mut self, paths: &[impl AsRef<Path>]) {
|
||||
// Associate all paths to its MIME type
|
||||
// This allows handling paths as groups if possible, such as launching a single video
|
||||
// player that is passed every path.
|
||||
let mut groups: HashMap<Mime, Vec<PathBuf>> = HashMap::new();
|
||||
for (mime, path) in paths
|
||||
.iter()
|
||||
.map(|path| (mime_icon::mime_for_path(path), path.as_ref().to_owned()))
|
||||
{
|
||||
groups.entry(mime).or_default().push(path);
|
||||
}
|
||||
|
||||
if mime == "application/x-desktop" {
|
||||
// Try opening desktop application
|
||||
match freedesktop_entry_parser::parse_entry(path) {
|
||||
Ok(entry) => match entry.section("Desktop Entry").attr("Exec") {
|
||||
Some(exec) => match mime_app::exec_to_command(exec, None) {
|
||||
Some(mut command) => match spawn_detached(&mut command) {
|
||||
Ok(()) => {
|
||||
return;
|
||||
'outer: for (mime, paths) in groups {
|
||||
log::debug!("Attempting to launch app\n\tfor: {mime}\n\twith: {paths:?}");
|
||||
|
||||
// First launch apps that can be launched directly
|
||||
if mime == "application/x-desktop" {
|
||||
// Try opening desktop application
|
||||
App::launch_desktop_entries(&paths);
|
||||
continue;
|
||||
} else if mime == "application/x-executable" || mime == "application/vnd.appimage" {
|
||||
// Try opening executable
|
||||
for path in paths {
|
||||
let mut command = std::process::Command::new(&path);
|
||||
match spawn_detached(&mut command) {
|
||||
Ok(()) => {}
|
||||
Err(err) => match err.kind() {
|
||||
io::ErrorKind::PermissionDenied => {
|
||||
// If permission is denied, try marking as executable, then running
|
||||
self.dialog_pages
|
||||
.push_back(DialogPage::SetExecutableAndLaunch {
|
||||
path: path.to_path_buf(),
|
||||
});
|
||||
}
|
||||
Err(err) => {
|
||||
_ => {
|
||||
log::warn!("failed to execute {:?}: {}", path, err);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try mime apps, which should be faster than xdg-open
|
||||
if self.launch_from_mime_cache(&mime, &paths) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// loop through subclasses if available
|
||||
if let Some(mime_sub_classes) = mime_icon::parent_mime_types(&mime) {
|
||||
for sub_class in mime_sub_classes {
|
||||
if self.launch_from_mime_cache(&sub_class, &paths) {
|
||||
continue 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to using open crate
|
||||
for path in paths {
|
||||
match open::that_detached(&path) {
|
||||
Ok(()) => {
|
||||
let _ = recently_used_xbel::update_recently_used(
|
||||
&path,
|
||||
App::APP_ID.to_string(),
|
||||
"cosmic-files".to_string(),
|
||||
None,
|
||||
);
|
||||
}
|
||||
Err(err) => {
|
||||
log::warn!("failed to open {:?}: {}", path, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn launch_desktop_entries(paths: &[impl AsRef<Path>]) {
|
||||
for path in paths.iter().map(AsRef::as_ref) {
|
||||
match freedesktop_entry_parser::parse_entry(path) {
|
||||
Ok(entry) => match entry.section("Desktop Entry").attr("Exec") {
|
||||
Some(exec) => match mime_app::exec_to_command(exec, &[] as &[&str; 0]) {
|
||||
Some(commands) => {
|
||||
for mut command in commands {
|
||||
if let Err(err) = spawn_detached(&mut command) {
|
||||
log::warn!("failed to execute {:?}: {}", path, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
log::warn!("failed to parse {:?}: invalid Desktop Entry/Exec", path);
|
||||
}
|
||||
|
|
@ -604,87 +677,48 @@ impl App {
|
|||
log::warn!("failed to parse {:?}: {}", path, err);
|
||||
}
|
||||
}
|
||||
} else if mime == "application/x-executable" || mime == "application/vnd.appimage" {
|
||||
// Try opening executable
|
||||
let mut command = std::process::Command::new(path);
|
||||
match spawn_detached(&mut command) {
|
||||
Ok(()) => {}
|
||||
Err(err) => match err.kind() {
|
||||
io::ErrorKind::PermissionDenied => {
|
||||
// If permission is denied, try marking as executable, then running
|
||||
self.dialog_pages
|
||||
.push_back(DialogPage::SetExecutableAndLaunch {
|
||||
path: path.to_path_buf(),
|
||||
});
|
||||
}
|
||||
_ => {
|
||||
log::warn!("failed to execute {:?}: {}", path, err);
|
||||
}
|
||||
},
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Try mime apps, which should be faster than xdg-open
|
||||
for app in self.mime_app_cache.get(&mime) {
|
||||
let Some(mut command) = app.command(Some(path.clone().into())) else {
|
||||
fn launch_from_mime_cache<P>(&self, mime: &Mime, paths: &[P]) -> bool
|
||||
where
|
||||
P: std::fmt::Debug + AsRef<Path> + AsRef<std::ffi::OsStr>,
|
||||
{
|
||||
for app in self.mime_app_cache.get(mime) {
|
||||
let Some(commands) = app.command(paths) else {
|
||||
continue;
|
||||
};
|
||||
match spawn_detached(&mut command) {
|
||||
Ok(()) => {
|
||||
let _ = recently_used_xbel::update_recently_used(
|
||||
path,
|
||||
App::APP_ID.to_string(),
|
||||
"cosmic-files".to_string(),
|
||||
None,
|
||||
);
|
||||
return;
|
||||
}
|
||||
Err(err) => {
|
||||
let len = commands.len();
|
||||
|
||||
for (i, mut command) in commands.into_iter().enumerate() {
|
||||
if let Err(err) = spawn_detached(&mut command) {
|
||||
// More than one command: The app doesn't support lists of paths so each command
|
||||
// is associated with one instance
|
||||
//
|
||||
// One command: Attempted to launch one app with multiple paths
|
||||
let path = if len > 1 {
|
||||
format!("{:?}", paths.get(i))
|
||||
} else {
|
||||
format!("{paths:?}")
|
||||
};
|
||||
log::warn!("failed to open {:?} with {:?}: {}", path, app.id, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// loop through subclasses if available
|
||||
if let Some(mime_sub_classes) = mime_icon::parent_mime_types(&mime) {
|
||||
for sub_class in mime_sub_classes {
|
||||
for app in self.mime_app_cache.get(&sub_class) {
|
||||
let Some(mut command) = app.command(Some(path.clone().into())) else {
|
||||
continue;
|
||||
};
|
||||
match spawn_detached(&mut command) {
|
||||
Ok(()) => {
|
||||
let _ = recently_used_xbel::update_recently_used(
|
||||
path,
|
||||
App::APP_ID.to_string(),
|
||||
"cosmic-files".to_string(),
|
||||
None,
|
||||
);
|
||||
return;
|
||||
}
|
||||
Err(err) => {
|
||||
log::warn!("failed to open {:?} with {:?}: {}", path, app.id, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to using open crate
|
||||
match open::that_detached(path) {
|
||||
Ok(()) => {
|
||||
for path in paths {
|
||||
let _ = recently_used_xbel::update_recently_used(
|
||||
path,
|
||||
&path.into(),
|
||||
App::APP_ID.to_string(),
|
||||
"cosmic-files".to_string(),
|
||||
None,
|
||||
);
|
||||
}
|
||||
Err(err) => {
|
||||
log::warn!("failed to open {:?}: {}", path, err);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// No app matched for mimes and paths
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(feature = "desktop")]
|
||||
|
|
@ -1769,7 +1803,7 @@ impl Application for App {
|
|||
dialog_text_input: widget::Id::unique(),
|
||||
key_binds,
|
||||
margin: HashMap::new(),
|
||||
mime_app_cache: mime_app::MimeAppCache::new(),
|
||||
mime_app_cache: MimeAppCache::new(),
|
||||
modifiers: Modifiers::empty(),
|
||||
mounter_items: HashMap::new(),
|
||||
network_drive_connecting: None,
|
||||
|
|
@ -2307,7 +2341,9 @@ impl Application for App {
|
|||
let all_apps = self.get_programs_for_mime(&mime);
|
||||
|
||||
if let Some(app) = all_apps.get(selected) {
|
||||
if let Some(mut command) = app.command(Some(path.clone().into())) {
|
||||
if let Some(mut command) =
|
||||
app.command(&[&path]).and_then(|v| v.into_iter().next())
|
||||
{
|
||||
match spawn_detached(&mut command) {
|
||||
Ok(()) => {
|
||||
let _ = recently_used_xbel::update_recently_used(
|
||||
|
|
@ -2739,18 +2775,18 @@ impl Application for App {
|
|||
}
|
||||
}
|
||||
for path in paths {
|
||||
if let Some(mut command) = terminal.command(None) {
|
||||
if let Some(mut command) = terminal
|
||||
.command::<&str>(&[])
|
||||
.and_then(|v| v.into_iter().next())
|
||||
{
|
||||
command.current_dir(&path);
|
||||
match spawn_detached(&mut command) {
|
||||
Ok(()) => {}
|
||||
Err(err) => {
|
||||
log::warn!(
|
||||
"failed to open {:?} with terminal {:?}: {}",
|
||||
path,
|
||||
terminal.id,
|
||||
err
|
||||
)
|
||||
}
|
||||
if let Err(err) = spawn_detached(&mut command) {
|
||||
log::warn!(
|
||||
"failed to open {:?} with terminal {:?}: {}",
|
||||
path,
|
||||
terminal.id,
|
||||
err
|
||||
)
|
||||
}
|
||||
} else {
|
||||
log::warn!("failed to get command for {:?}", terminal.id);
|
||||
|
|
@ -2800,12 +2836,12 @@ impl Application for App {
|
|||
..
|
||||
}) => {
|
||||
let url = format!("mime:///{mime}");
|
||||
if let Some(mut command) = app.command(Some(url.clone().into())) {
|
||||
match spawn_detached(&mut command) {
|
||||
Ok(()) => {}
|
||||
Err(err) => {
|
||||
log::warn!("failed to open {:?} with {:?}: {}", url, app.id, err)
|
||||
}
|
||||
// TODO: Support multiple URLs
|
||||
if let Some(mut command) =
|
||||
app.command(&[&url]).and_then(|v| v.into_iter().next())
|
||||
{
|
||||
if let Err(err) = spawn_detached(&mut command) {
|
||||
log::warn!("failed to open {:?} with {:?}: {}", url, app.id, err)
|
||||
}
|
||||
} else {
|
||||
log::warn!(
|
||||
|
|
@ -3329,7 +3365,7 @@ impl Application for App {
|
|||
cosmic::action::app(Message::TabMessage(Some(entity), x))
|
||||
}));
|
||||
}
|
||||
tab::Command::OpenFile(path) => self.open_file(&path),
|
||||
tab::Command::OpenFile(paths) => self.open_file(&paths),
|
||||
tab::Command::OpenInNewTab(path) => {
|
||||
commands.push(self.open_tab(Location::Path(path.clone()), false, None));
|
||||
}
|
||||
|
|
@ -3682,9 +3718,9 @@ impl Application for App {
|
|||
.nav_model
|
||||
.data::<Location>(entity)
|
||||
.and_then(|x| x.path_opt())
|
||||
.map(|x| x.to_path_buf())
|
||||
.map(ToOwned::to_owned)
|
||||
{
|
||||
self.open_file(&path);
|
||||
self.open_file(&[path]);
|
||||
}
|
||||
}
|
||||
NavMenuAction::OpenWith(entity) => {
|
||||
|
|
@ -4427,7 +4463,7 @@ impl Application for App {
|
|||
};
|
||||
|
||||
let mut column = widget::list_column();
|
||||
let available_programs = self.get_programs_for_mime(&mime);
|
||||
let available_programs = self.get_programs_for_mime(mime);
|
||||
let item_height = 32.0;
|
||||
let mut displayed_default = false;
|
||||
|
||||
|
|
|
|||
129
src/mime_app.rs
129
src/mime_app.rs
|
|
@ -6,32 +6,123 @@ use cosmic::desktop;
|
|||
use cosmic::widget;
|
||||
pub use mime_guess::Mime;
|
||||
use std::{
|
||||
cmp::Ordering, collections::HashMap, env, ffi::OsString, fs, io, path::PathBuf, process,
|
||||
cmp::Ordering,
|
||||
collections::HashMap,
|
||||
env,
|
||||
ffi::OsStr,
|
||||
fs, io,
|
||||
path::{Path, PathBuf},
|
||||
process,
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
pub fn exec_to_command(exec: &str, path_opt: Option<OsString>) -> Option<process::Command> {
|
||||
let args_vec: Vec<String> = shlex::split(exec)?;
|
||||
let mut args = args_vec.iter();
|
||||
let mut command = process::Command::new(args.next()?);
|
||||
for arg in args {
|
||||
if arg.starts_with('%') {
|
||||
match arg.as_str() {
|
||||
"%f" | "%F" | "%u" | "%U" => {
|
||||
if let Some(path) = &path_opt {
|
||||
command.arg(path);
|
||||
}
|
||||
// Supported exec key field codes
|
||||
const EXEC_HANDLERS: [&str; 4] = ["%f", "%F", "%u", "%U"];
|
||||
// Deprecated field codes. The spec advises to ignore these handlers.
|
||||
const DEPRECATED_HANDLERS: [&str; 6] = ["%d", "%D", "%n", "%N", "%v", "%m"];
|
||||
|
||||
pub fn exec_to_command(
|
||||
exec: &str,
|
||||
path_opt: &[impl AsRef<OsStr>],
|
||||
) -> Option<Vec<process::Command>> {
|
||||
let args_vec = shlex::split(exec)?;
|
||||
let program = args_vec.first()?;
|
||||
|
||||
// Base Command instance(s)
|
||||
// 1. We may need to launch multiple of the same process.
|
||||
// 2. Each of those processes will need to be passed args from exec.
|
||||
// 3. Each of those args may appear in any order.
|
||||
//
|
||||
// So, we'll go through exec in two passes. The first pass handles paths (%f etc) while the
|
||||
// second passes extra, non-% args to each processes.
|
||||
//
|
||||
// While it'd be marginally faster to process everything in one pass, that's problematic:
|
||||
// 1. path_opt may need to be cloned because it may be moved on each iteration (borrowck
|
||||
// doesn't know we'll only use it once)
|
||||
// 2. We have to keep track of which modifier (%f etc) we've used/seen already
|
||||
// 3. We have to keep track of which processes received non-modifier args which gets messy fast
|
||||
// 4. `exec` is likely small so looping over it twice is not a big deal
|
||||
let args_handler = args_vec
|
||||
.iter()
|
||||
.find(|arg| EXEC_HANDLERS.contains(&arg.as_str()));
|
||||
// msrv
|
||||
// .inspect(|handler| log::trace!("Found paths handler: {handler} for exec: {exec}"));
|
||||
let mut processes = match args_handler.map(|s| s.as_str()) {
|
||||
Some("%f") => {
|
||||
let mut processes = Vec::with_capacity(path_opt.len());
|
||||
|
||||
for path in path_opt.iter().map(AsRef::as_ref) {
|
||||
// TODO: %f and %F need to handle non-file URLs (see spec)
|
||||
if from_file_or_dir(path).is_none() {
|
||||
log::warn!("Desktop file expects a file path instead of a URL: {path:?}");
|
||||
}
|
||||
_ => {
|
||||
log::warn!("unsupported Exec code {:?} in {:?}", arg, exec);
|
||||
return None;
|
||||
|
||||
// Passing multiple paths to %f should open an instance per path
|
||||
let mut process = process::Command::new(program);
|
||||
process.arg(path);
|
||||
processes.push(process);
|
||||
}
|
||||
|
||||
processes
|
||||
}
|
||||
Some("%F") => {
|
||||
// TODO: %f and %F need to handle non-file URLs (see spec)
|
||||
for invalid in path_opt
|
||||
.iter()
|
||||
.map(AsRef::as_ref)
|
||||
.filter(|path| from_file_or_dir(path).is_none())
|
||||
{
|
||||
log::warn!("Desktop file expects a file path instead of a URL: {invalid:?}");
|
||||
}
|
||||
|
||||
// Launch one instance with all args
|
||||
let mut process = process::Command::new(program);
|
||||
process.args(path_opt);
|
||||
|
||||
vec![process]
|
||||
}
|
||||
Some("%u") => path_opt
|
||||
.iter()
|
||||
.map(|path| {
|
||||
let mut process = process::Command::new(program);
|
||||
process.arg(path);
|
||||
process
|
||||
})
|
||||
.collect(),
|
||||
Some("%U") => {
|
||||
let mut process = process::Command::new(program);
|
||||
process.args(path_opt);
|
||||
vec![process]
|
||||
}
|
||||
Some(invalid) => unreachable!("All valid variants were checked; got: {invalid}"),
|
||||
None => vec![process::Command::new(program)],
|
||||
};
|
||||
|
||||
// Pass 2: Add every argument that's not % to each process
|
||||
for arg in args_vec.into_iter().skip(1) {
|
||||
match arg.as_str() {
|
||||
unsupported
|
||||
if arg.starts_with('%')
|
||||
&& !EXEC_HANDLERS.contains(&unsupported)
|
||||
&& !DEPRECATED_HANDLERS.contains(&unsupported) =>
|
||||
{
|
||||
log::warn!("unsupported Exec code {:?} in {:?}", unsupported, exec);
|
||||
return None;
|
||||
}
|
||||
arg => {
|
||||
for process in &mut processes {
|
||||
process.arg(arg);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
command.arg(arg);
|
||||
}
|
||||
}
|
||||
Some(command)
|
||||
Some(processes)
|
||||
}
|
||||
|
||||
fn from_file_or_dir(path: impl AsRef<Path>) -> Option<url::Url> {
|
||||
url::Url::from_file_path(&path)
|
||||
.ok()
|
||||
.or_else(|| url::Url::from_directory_path(&path).ok())
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
|
@ -46,7 +137,7 @@ pub struct MimeApp {
|
|||
|
||||
impl MimeApp {
|
||||
//TODO: move to libcosmic, support multiple files
|
||||
pub fn command(&self, path_opt: Option<OsString>) -> Option<process::Command> {
|
||||
pub fn command<O: AsRef<OsStr>>(&self, path_opt: &[O]) -> Option<Vec<process::Command>> {
|
||||
exec_to_command(self.exec.as_deref()?, path_opt)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
23
src/tab.rs
23
src/tab.rs
|
|
@ -1143,7 +1143,7 @@ pub enum Command {
|
|||
#[cfg(feature = "desktop")]
|
||||
ExecEntryAction(cosmic::desktop::DesktopEntryData, usize),
|
||||
Iced(TaskWrapper),
|
||||
OpenFile(PathBuf),
|
||||
OpenFile(Vec<PathBuf>),
|
||||
OpenInNewTab(PathBuf),
|
||||
OpenInNewWindow(PathBuf),
|
||||
OpenTrash,
|
||||
|
|
@ -2361,7 +2361,7 @@ impl Tab {
|
|||
if clicked_item.metadata.is_dir() {
|
||||
cd = Some(location.clone());
|
||||
} else if let Some(path) = location.path_opt() {
|
||||
commands.push(Command::OpenFile(path.to_path_buf()));
|
||||
commands.push(Command::OpenFile(vec![path.to_path_buf()]));
|
||||
} else {
|
||||
log::warn!("no path for item {:?}", clicked_item);
|
||||
}
|
||||
|
|
@ -2467,6 +2467,7 @@ impl Tab {
|
|||
.any(|(e_i, e)| Some(e_i) == click_i_opt.as_ref() && e.selected)
|
||||
});
|
||||
if let Some(ref mut items) = self.items_opt {
|
||||
let mut paths_to_open = vec![];
|
||||
for (i, item) in items.iter_mut().enumerate() {
|
||||
if Some(i) == click_i_opt {
|
||||
// Single click to open.
|
||||
|
|
@ -2475,7 +2476,7 @@ impl Tab {
|
|||
if item.metadata.is_dir() {
|
||||
cd = Some(location.clone());
|
||||
} else if let Some(path) = location.path_opt() {
|
||||
commands.push(Command::OpenFile(path.to_path_buf()));
|
||||
paths_to_open.push(path.to_path_buf());
|
||||
} else {
|
||||
log::warn!("no path for item {:?}", item);
|
||||
}
|
||||
|
|
@ -2508,6 +2509,9 @@ impl Tab {
|
|||
item.selected = false;
|
||||
}
|
||||
}
|
||||
if !paths_to_open.is_empty() {
|
||||
commands.push(Command::OpenFile(paths_to_open));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2897,7 +2901,7 @@ impl Tab {
|
|||
if path.is_dir() {
|
||||
cd = Some(location);
|
||||
} else {
|
||||
commands.push(Command::OpenFile(path.clone()));
|
||||
commands.push(Command::OpenFile(vec![path.clone()]));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
|
|
@ -2920,11 +2924,12 @@ impl Tab {
|
|||
if path.is_dir() {
|
||||
cd = Some(Location::Path(path));
|
||||
} else {
|
||||
commands.push(Command::OpenFile(path));
|
||||
commands.push(Command::OpenFile(vec![path]));
|
||||
}
|
||||
}
|
||||
None => {
|
||||
if let Some(ref mut items) = self.items_opt {
|
||||
let mut open_files = Vec::new();
|
||||
for item in items.iter() {
|
||||
if item.selected {
|
||||
if let Some(location) = &item.location_opt {
|
||||
|
|
@ -2932,13 +2937,15 @@ impl Tab {
|
|||
//TODO: allow opening multiple tabs?
|
||||
cd = Some(location.clone());
|
||||
} else if let Some(path) = location.path_opt() {
|
||||
commands.push(Command::OpenFile(path.to_path_buf()));
|
||||
open_files.push(path.to_path_buf());
|
||||
}
|
||||
} else {
|
||||
//TODO: open properties?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
commands.push(Command::OpenFile(open_files));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2978,7 +2985,7 @@ impl Tab {
|
|||
//cd = Some(Location::Path(path.clone()));
|
||||
commands.push(Command::OpenInNewTab(path.clone()))
|
||||
} else {
|
||||
commands.push(Command::OpenFile(path.clone()));
|
||||
commands.push(Command::OpenFile(vec![path.clone()]));
|
||||
}
|
||||
} else {
|
||||
log::warn!("no path for item {:?}", clicked_item);
|
||||
|
|
@ -3310,7 +3317,7 @@ impl Tab {
|
|||
if matches!(self.mode, Mode::Desktop) {
|
||||
match location {
|
||||
Location::Path(path) => {
|
||||
commands.push(Command::OpenFile(path));
|
||||
commands.push(Command::OpenFile(vec![path]));
|
||||
}
|
||||
Location::Trash => {
|
||||
commands.push(Command::OpenTrash);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue