diff --git a/src/apps/user.rs b/src/apps/user.rs index 26723be..a778673 100644 --- a/src/apps/user.rs +++ b/src/apps/user.rs @@ -91,10 +91,12 @@ impl MimeApps { let handler = self.get_handler(mime)?; let output = if output_json { let entry = handler.get_entry()?; + let cmd = entry.get_cmd(vec![])?; + (json::object! { handler: handler.to_string(), name: entry.name.as_str(), - cmd: entry.get_cmd(vec![])?.0 + cmd: cmd.0 + " " + &cmd.1.join(" "), }) .to_string() } else { diff --git a/src/cli.rs b/src/cli.rs index d4f2427..4b00cbb 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,4 +1,4 @@ -use crate::common::{Handler, MimeOrExtension}; +use crate::common::{Handler, MimeOrExtension, UserPath}; #[derive(clap::Clap)] #[clap(global_setting = clap::AppSettings::DeriveDisplayOrder)] @@ -14,7 +14,7 @@ pub enum Cmd { /// Open a path/URL with its default handler Open { #[clap(required = true)] - paths: Vec, + paths: Vec, }, /// Set the default handler for mime/extension diff --git a/src/common/db.rs b/src/common/db.rs index bd2b655..c957040 100644 --- a/src/common/db.rs +++ b/src/common/db.rs @@ -4,6 +4,7 @@ static CUSTOM_MIMES: &[&'static str] = &[ "inode/directory", "x-scheme-handler/http", "x-scheme-handler/https", + "x-scheme-handler/terminal", ]; pub fn autocomplete() -> Result<()> { diff --git a/src/common/desktop_entry.rs b/src/common/desktop_entry.rs index 2c6209b..382f07b 100644 --- a/src/common/desktop_entry.rs +++ b/src/common/desktop_entry.rs @@ -59,38 +59,50 @@ impl DesktopEntry { Ok(()) } pub fn get_cmd(&self, args: Vec) -> Result<(String, Vec)> { - let cmd_patterns = &["%f", "%F", "%u", "%U"]; - let special = AhoCorasick::new_auto_configured(cmd_patterns); + let special = + AhoCorasick::new_auto_configured(&["%f", "%F", "%u", "%U"]); - let mut split = shlex::split(&self.exec) - .unwrap() - .into_iter() - .flat_map(|s| match s.as_str() { - "%f" | "%F" | "%u" | "%U" => args.clone(), - s if special.is_match(s) => vec![{ - let mut replaced = String::with_capacity(s.len()); - special.replace_all_with(s, &mut replaced, |_, _, dst| { - dst.push_str(args.clone().join(" ").as_str()); - false - }); - replaced - }], - _ => vec![s], - }) - .collect::>(); + let mut exec = shlex::split(&self.exec).unwrap(); + + // The desktop entry doesn't contain arguments - we make best effort and append them at + // the end + if special.is_match(&self.exec) { + exec = exec + .into_iter() + .flat_map(|s| match s.as_str() { + "%f" | "%F" | "%u" | "%U" => args.clone(), + s if special.is_match(s) => vec![{ + let mut replaced = + String::with_capacity(s.len() + args.len() * 2); + special.replace_all_with( + s, + &mut replaced, + |_, _, dst| { + dst.push_str(args.clone().join(" ").as_str()); + false + }, + ); + replaced + }], + _ => vec![s], + }) + .collect() + } else { + exec.extend_from_slice(&args); + } // If the entry expects a terminal (emulator), but this process is not running in one, we // launch a new one. if self.terminal && !atty::is(atty::Stream::Stdout) { - split = shlex::split(&crate::config::Config::terminal()?) + exec = shlex::split(&crate::config::Config::terminal()?) .unwrap() .into_iter() .chain(vec!["-e".to_owned()]) - .chain(split.into_iter()) + .chain(exec) .collect(); } - Ok((split.remove(0), split)) + Ok((exec.remove(0), exec)) } } diff --git a/src/common/handler.rs b/src/common/handler.rs index 7ac1c80..9e505c2 100644 --- a/src/common/handler.rs +++ b/src/common/handler.rs @@ -2,18 +2,18 @@ use crate::{ common::{DesktopEntry, ExecMode}, Error, Result, }; -use std::{convert::TryFrom, ffi::OsString, path::PathBuf}; +use std::{fmt::Display, convert::TryFrom, ffi::OsString, path::PathBuf, str::FromStr}; #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct Handler(OsString); -impl std::fmt::Display for Handler { +impl Display for Handler { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(&self.0.to_string_lossy()) } } -impl std::str::FromStr for Handler { +impl FromStr for Handler { type Err = Error; fn from_str(s: &str) -> Result { Self::resolve(s.into()) diff --git a/src/common/mime_types.rs b/src/common/mime_types.rs index 9bd5668..ed9d076 100644 --- a/src/common/mime_types.rs +++ b/src/common/mime_types.rs @@ -1,10 +1,7 @@ use crate::{Error, Result}; use mime::Mime; -use std::{ - convert::TryFrom, - path::{Path}, - str::FromStr, -}; +use std::{convert::TryFrom, path::Path, str::FromStr}; +use url::Url; // A mime derived from a path or URL #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] @@ -24,21 +21,26 @@ impl MimeType { } } -impl TryFrom<&str> for MimeType { - type Error = Error; +impl From<&Url> for MimeType { + fn from(url: &Url) -> Self { + Self( + format!("x-scheme-handler/{}", url.scheme()) + .parse::() + .unwrap(), + ) + } +} - fn try_from(arg: &str) -> Result { - match url::Url::parse(arg) { - Ok(url) if url.scheme() == "file" => { - Self::try_from(Path::new(url.path())) - } - Ok(url) => Ok(Self( - format!("x-scheme-handler/{}", url.scheme()) - .parse::() - .unwrap(), - )), - Err(_) => Self::try_from(Path::new(arg)), - } +impl TryFrom<&Path> for MimeType { + type Error = Error; + fn try_from(path: &Path) -> Result { + let db = xdg_mime::SharedMimeInfo::new(); + let guess = db.guess_mime_type().path(&path).guess(); + + let mime = mime_to_option(guess.mime_type().clone()) + .ok_or_else(|| Error::Ambiguous(path.to_owned()))?; + + Ok(Self(mime)) } } @@ -50,20 +52,6 @@ fn mime_to_option(mime: Mime) -> Option { } } -impl TryFrom<&Path> for MimeType { - type Error = Error; - fn try_from(path: &Path) -> Result { - let db = xdg_mime::SharedMimeInfo::new(); - - let guess = db.guess_mime_type().path(&path).guess(); - - let mime = mime_to_option(guess.mime_type().clone()) - .ok_or_else(|| Error::Ambiguous(path.to_owned()))?; - - Ok(Self(mime)) - } -} - // Mime derived from user input: extension(.pdf) or type like image/jpg #[derive(Debug)] pub struct MimeOrExtension(pub Mime); diff --git a/src/common/mod.rs b/src/common/mod.rs index 18957f8..3f065e2 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -2,8 +2,10 @@ mod db; mod desktop_entry; mod handler; mod mime_types; +mod path; pub use self::db::autocomplete as db_autocomplete; pub use desktop_entry::{DesktopEntry, Mode as ExecMode}; pub use handler::Handler; pub use mime_types::{MimeOrExtension, MimeType}; +pub use path::UserPath; diff --git a/src/common/path.rs b/src/common/path.rs new file mode 100644 index 0000000..1a8b9e8 --- /dev/null +++ b/src/common/path.rs @@ -0,0 +1,51 @@ +use url::Url; + +use crate::{common::MimeType, Error, Result}; +use std::{ + convert::TryFrom, + fmt::{Display, Formatter}, + path::PathBuf, + str::FromStr, +}; + +pub enum UserPath { + Url(Url), + File(PathBuf), +} + +impl UserPath { + pub fn get_mime(&self) -> Result { + match self { + Self::Url(url) => Ok(url.into()), + Self::File(f) => MimeType::try_from(f.as_path()), + } + } +} + +impl FromStr for UserPath { + type Err = Error; + fn from_str(s: &str) -> Result { + let normalized = match url::Url::parse(&s) { + Ok(url) if url.scheme() == "file" => { + let path = url + .to_file_path() + .map_err(|_| Error::BadPath(url.path().to_owned()))?; + + Self::File(path) + } + Ok(url) => Self::Url(url), + _ => Self::File(PathBuf::from(s)), + }; + + Ok(normalized) + } +} + +impl Display for UserPath { + fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { + match self { + Self::File(f) => fmt.write_str(&f.to_string_lossy().to_string()), + Self::Url(u) => fmt.write_str(&u.to_string()), + } + } +} diff --git a/src/config.rs b/src/config.rs index 9e0769d..57972ff 100644 --- a/src/config.rs +++ b/src/config.rs @@ -54,8 +54,6 @@ impl Config { ); apps.save().ok()?; - - Some(entry.1) }) .map(|e| e.exec) diff --git a/src/error.rs b/src/error.rs index 2225f3b..c5cee4b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -22,9 +22,10 @@ pub enum Error { Selector(String), #[error("selection cancelled")] Cancelled, - #[error("Please specify the default terminal with handlr set x-scheme-handler/terminal")] NoTerminal, + #[error("Bad path: {0}")] + BadPath(String), } pub type Result = std::result::Result; diff --git a/src/main.rs b/src/main.rs index 490aa5b..1c99069 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,8 +12,8 @@ mod utils; fn main() -> Result<()> { use clap::Clap; use cli::Cmd; - use common::{Handler, MimeType}; - use std::{collections::HashMap, convert::TryFrom}; + use common::{Handler}; + use std::{collections::HashMap}; // create config if it doesn't exist Lazy::force(&CONFIG); @@ -41,17 +41,10 @@ fn main() -> Result<()> { HashMap::new(); for path in paths.into_iter() { - let path = match url::Url::parse(&path) { - Ok(url) if url.scheme() == "file" => { - url.path().to_owned() - } - _ => path, - }; - - let mime = MimeType::try_from(path.as_str())?.0; - let handler = apps.get_handler(&mime)?; - - handlers.entry(handler).or_default().push(path); + handlers + .entry(apps.get_handler(&path.get_mime()?.0)?) + .or_default() + .push(path.to_string()); } for (handler, paths) in handlers.into_iter() {