diff --git a/src/file_browser.rs b/src/file_browser.rs index 91194f2..558e5a6 100644 --- a/src/file_browser.rs +++ b/src/file_browser.rs @@ -273,6 +273,25 @@ impl FileBrowser { Ok(()) } + pub fn open_bg(&mut self) -> HResult<()> { + let cwd = self.cwd()?; + let file = self.selected_file()?; + + let cmd = crate::proclist::Cmd { + cmd: OsString::from(file.strip_prefix(&cwd)), + short_cmd: None, + args: None, + cwd: cwd.clone(), + cwd_files: None, + tab_files: None, + tab_paths: None + }; + + self.proc_view.lock()?.run_proc_raw(cmd)?; + + Ok(()) + } + pub fn main_widget_goto(&mut self, dir: &File) -> HResult<()> { match dir.is_readable() { Ok(true) => {}, @@ -665,58 +684,28 @@ impl FileBrowser { fn exec_cmd(&mut self, tab_dirs: Vec, tab_files: Vec>) -> HResult<()> { - let cwd = self.cwd()?; - let filename = self.selected_file()?.path.quoted_file_name()?; + + let cwd = self.cwd()?.clone(); + let selected_file = self.selected_file()?; let selected_files = self.selected_files()?; - let files = selected_files.iter() - .map(|f| f.path()) - .collect::>(); + let cmd = self.minibuffer("exec")?.trim_start().to_string() + " "; - let cmd = self.minibuffer("exec")?.trim_start().to_string(); + let cwd_files = if selected_files.len() == 0 { + vec![selected_file] + } else { selected_files }; - let cmd = OsString::from(cmd); - let space = OsString::from(" "); - - let mut cmd = if files.len() == 0 { - cmd.replace(&OsString::from("$s"), &filename) - } else { - let args = files.iter() - .fold(OsString::new(), |mut args, file| { - if let Some(name) = file.quoted_file_name() { - args.push(name); - args.push(space.clone()); - } - args - }); - let args = args.trim_last_space(); - - cmd.replace(&OsString::from("$s"), &args) + let cmd = crate::proclist::Cmd { + cmd: OsString::from(cmd), + short_cmd: None, + args: None, + cwd: cwd, + cwd_files: Some(cwd_files), + tab_files: Some(tab_files), + tab_paths: Some(tab_dirs) }; - for (i, tab_dir) in tab_dirs.iter().enumerate() { - if let Some(tab_files) = tab_files.get(i) { - let tab_file_identifier = OsString::from(format!("${}s", i)); - - let args = tab_files.iter() - .fold(OsString::new(), |mut args, file| { - let file_path = file.strip_prefix(&cwd); - let name = file_path.quoted_path(); - args.push(name); - args.push(space.clone()); - - args - }); - - cmd = cmd.replace(&tab_file_identifier, &args); - } - - let tab_identifier = OsString::from(format!("${}", i)); - let tab_path = tab_dir.path().into_os_string(); - cmd = cmd.replace(&tab_identifier, &tab_path); - } - - self.proc_view.lock()?.run_proc(&cmd)?; + self.proc_view.lock()?.run_proc_subshell(cmd)?; Ok(()) } @@ -856,6 +845,7 @@ impl Widget for FileBrowser { Key::Char('/') => { self.turbo_cd()?; }, Key::Char('Q') => { self.quit_with_dir()?; }, Key::Right | Key::Char('f') => { self.enter_dir()?; }, + Key::Char('F') => { self.open_bg()?; }, Key::Left | Key::Char('b') => { self.go_back()?; }, Key::Char('-') => { self.goto_prev_cwd()?; }, Key::Char('`') => { self.goto_bookmark()?; }, diff --git a/src/files.rs b/src/files.rs index 54e9049..1a038ac 100644 --- a/src/files.rs +++ b/src/files.rs @@ -517,6 +517,10 @@ impl File { } pub fn strip_prefix(&self, base: &File) -> PathBuf { + if self == base { + return PathBuf::from("./"); + } + let base_path = base.path.clone(); match self.path.strip_prefix(base_path) { Ok(path) => PathBuf::from(path), @@ -741,13 +745,105 @@ impl PathBufExt for PathBuf { } pub trait OsStrTools { + fn split(&self, pat: &OsStr) -> Vec; fn replace(&self, from: &OsStr, to: &OsStr) -> OsString; fn trim_last_space(&self) -> OsString; fn contains_osstr(&self, pat: &OsStr) -> bool; fn position(&self, pat: &OsStr) -> Option; + fn splice_quoted(&self, from: &OsStr, to: Vec) -> Vec; + fn splice_with(&self, from: &OsStr, to: Vec) -> Vec; + fn quote(&self) -> OsString; } impl OsStrTools for OsStr { + fn split(&self, pat: &OsStr) -> Vec { + let orig_string = self.as_bytes().to_vec(); + let pat = pat.as_bytes().to_vec(); + let pat_len = pat.len(); + + dbg!(&self); + + let split_string = orig_string + .windows(pat_len) + .enumerate() + .fold(Vec::new(), |mut split_pos, (i, chars)| { + dbg!(&chars); + dbg!(&split_pos); + if chars == pat.as_slice() { + if split_pos.len() == 0 { + split_pos.push((0, i)); + } else { + let len = split_pos.len(); + let last_split = split_pos[len-1].1; + split_pos.push((last_split, i)); + } + } + split_pos + }).iter() + .map(|(start, end)| { + //let orig_string = orig_string.clone(); + OsString::from_vec(orig_string[*start..*end] + .to_vec()).replace(&OsString::from_vec(pat.clone()), + &OsString::from("")) + }).collect(); + split_string + } + + + fn quote(&self) -> OsString { + let mut string = self.as_bytes().to_vec(); + let mut quote = "\"".as_bytes().to_vec(); + + let mut quoted = vec![]; + quoted.append(&mut quote.clone()); + quoted.append(&mut string); + quoted.append(&mut quote); + + OsString::from_vec(quoted) + } + + fn splice_quoted(&self, from: &OsStr, to: Vec) -> Vec { + let quoted_to = to.iter() + .map(|to| to.quote()) + .collect(); + self.splice_with(from, quoted_to) + } + + fn splice_with(&self, from: &OsStr, to: Vec) -> Vec { + let pos = self.position(from); + + if pos.is_none() { + return vec![OsString::from(self)]; + } + + dbg!(&self); + + let pos = pos.unwrap(); + let string = self.as_bytes().to_vec(); + let from = from.as_bytes().to_vec(); + let fromlen = from.len(); + + let lpart = OsString::from_vec(string[0..pos].to_vec()); + let rpart = OsString::from_vec(string[pos+fromlen..].to_vec()); + + dbg!(&lpart); + dbg!(&rpart); + + let mut result = vec![ + vec![lpart.trim_last_space()], + to, + vec![rpart] + ].into_iter() + .flatten() + .filter(|part| part.len() != 0) + .collect::>(); + + if result.last() == Some(&OsString::from("")) { + result.pop(); + result + } else { result } + } + fn replace(&self, from: &OsStr, to: &OsStr) -> OsString { let orig_string = self.as_bytes().to_vec(); let from = from.as_bytes(); diff --git a/src/paths.rs b/src/paths.rs index b3f3e1d..8917756 100644 --- a/src/paths.rs +++ b/src/paths.rs @@ -10,9 +10,15 @@ pub fn home_path() -> HResult { } pub fn hunter_path() -> HResult { - let mut config_dir = dirs_2::config_dir()?; - config_dir.push("hunter/"); - Ok(config_dir) + let mut hunter_path = dirs_2::config_dir()?; + hunter_path.push("hunter/"); + Ok(hunter_path) +} + +pub fn config_path() -> HResult { + let mut config_path = hunter_path()?; + config_path.push("config"); + Ok(config_path) } pub fn bookmark_path() -> HResult { diff --git a/src/proclist.rs b/src/proclist.rs index a841b44..a4efabf 100644 --- a/src/proclist.rs +++ b/src/proclist.rs @@ -4,6 +4,7 @@ use std::process::Child; use std::os::unix::process::{CommandExt, ExitStatusExt}; use std::io::{BufRead, BufReader}; use std::ffi::OsString; +use std::os::unix::ffi::{OsStringExt, OsStrExt}; use termion::event::Key; use unicode_width::UnicodeWidthStr; @@ -17,7 +18,7 @@ use crate::hbox::HBox; use crate::preview::WillBeWidget; use crate::fail::{HResult, HError, ErrorLog}; use crate::term; -use crate::files::OsStrTools; +use crate::files::{File, OsStrTools}; #[derive(Debug)] struct Process { @@ -30,6 +31,75 @@ struct Process { } +pub struct Cmd { + pub cmd: OsString, + pub args: Option>, + pub short_cmd: Option, + pub cwd: File, + pub cwd_files: Option>, + pub tab_files: Option>>, + pub tab_paths: Option>, +} + +impl Cmd { + fn process(&mut self) -> Vec { + let cmd = self.cmd.clone().split(&OsString::from(" ")); + let cmd = self.substitute_cwd_files(cmd); + let cmd = self.substitute_tab_files(cmd); + let cmd = self.substitute_tab_paths(cmd); + cmd + } + + fn substitute_cwd_files(&mut self, cmd: Vec) -> Vec { + let cwd_pat = OsString::from("$s"); + let cwd_files = self.cwd_files + .take() + .unwrap() + .iter() + .map(|file| file.strip_prefix(&self.cwd).into_os_string()) + .collect::>(); + + cmd.iter() + .map(|part| part.splice_quoted(&cwd_pat, + cwd_files.clone())) + .flatten().collect() + } + + fn substitute_tab_files(&mut self, cmd: Vec) -> Vec { + let tab_files = self.tab_files.take().unwrap(); + + tab_files.into_iter() + .enumerate() + .fold(cmd, |cmd, (i, tab_files)| { + let tab_files_pat = OsString::from(format!("${}s", i)); + let tab_file_paths = tab_files.iter() + .map(|file| file.strip_prefix(&self.cwd).into_os_string()) + .collect::>(); + + cmd.iter().map(|part| { + part.splice_quoted(&tab_files_pat, + tab_file_paths.clone()) + }).flatten().collect() + }) + } + + fn substitute_tab_paths(&mut self, cmd: Vec) -> Vec { + let tab_paths = self.tab_paths.take().unwrap(); + + tab_paths.into_iter() + .enumerate() + .fold(cmd, |cmd, (i, tab_path)| { + let tab_path_pat = OsString::from(format!("${}", i)); + let tab_path = tab_path.strip_prefix(&self.cwd).into_os_string(); + + cmd.iter().map(|part| { + part.splice_quoted(&tab_path_pat, + vec![tab_path.clone()]) + }).flatten().collect() + }) + } +} + impl PartialEq for Process { fn eq(&self, other: &Process) -> bool { self.cmd == other.cmd @@ -120,17 +190,44 @@ impl Listable for ListView> { } impl ListView> { - fn run_proc(&mut self, cmd: &OsString) -> HResult<()> { + fn run_proc_subshell(&mut self, mut cmd: Cmd) -> HResult<()> { let shell = std::env::var("SHELL").unwrap_or("sh".into()); let home = crate::paths::home_path()?.into_os_string(); + + let cmd_args = cmd.process(); + let short = OsString::from("~"); - let short_cmd = cmd.replace(&home, &short).to_string_lossy().to_string(); + let short_cmd = cmd_args + .concat() + .replace(&home, &short) + .replace(&OsString::from("\""), &OsString::from("")) + .to_string_lossy() + .to_string(); self.show_status(&format!("Running: {}", &short_cmd)).log(); - let handle = std::process::Command::new(shell) - .arg("-c") - .arg(cmd) + let shell_args = cmd_args.concat(); + let shell_args = vec![OsString::from("-c"), shell_args.clone()]; + + cmd.cmd = OsString::from(shell.clone()); + cmd.args = Some(shell_args.clone()); + cmd.short_cmd = Some(short_cmd); + + self.run_proc_raw(cmd) + } + + fn run_proc_raw(&mut self, cmd: Cmd) -> HResult<()> { + let real_cmd = cmd.cmd; + let short_cmd = cmd.short_cmd + .unwrap_or(real_cmd + .to_string_lossy() + .to_string()); + let args = cmd.args.unwrap_or(vec![]); + + self.show_status(&format!("Running: {}", &short_cmd)).log(); + + let handle = std::process::Command::new(real_cmd) + .args(args) .stdin(std::process::Stdio::null()) .stdout(std::process::Stdio::piped()) .before_exec(|| unsafe { libc::dup2(1, 2); Ok(()) }) @@ -289,8 +386,13 @@ impl ProcView { self.hbox.get_textview() } - pub fn run_proc(&mut self, cmd: &OsString) -> HResult<()> { - self.get_listview_mut().run_proc(cmd)?; + pub fn run_proc_subshell(&mut self, cmd: Cmd) -> HResult<()> { + self.get_listview_mut().run_proc_subshell(cmd)?; + Ok(()) + } + + pub fn run_proc_raw(&mut self, cmd: Cmd) -> HResult<()> { + self.get_listview_mut().run_proc_raw(cmd)?; Ok(()) } @@ -467,3 +569,50 @@ impl Widget for ProcView { Ok(()) } } + + +trait ConcatOsString { + fn concat(&self) -> OsString; + fn concat_quoted(&self) -> OsString; +} + +impl ConcatOsString for Vec { + fn concat(&self) -> OsString { + let len = self.len(); + self.iter().enumerate().fold(OsString::new(), |string, (i, part)| { + let mut string = string.into_vec(); + let mut space = " ".as_bytes().to_vec(); + let mut part = part.clone().into_vec(); + + string.append(&mut part); + + if i != len { + string.append(&mut space); + } + + OsString::from_vec(string) + }) + } + + fn concat_quoted(&self) -> OsString { + let len = self.len(); + self.iter().enumerate().fold(OsString::new(), |string, (i, part)| { + let mut string = string.into_vec(); + let mut space = " ".as_bytes().to_vec(); + let mut quote = "\"".as_bytes().to_vec(); + let mut part = part.clone().into_vec(); + + + string.append(&mut quote.clone()); + string.append(&mut part); + string.append(&mut quote); + + + if i+1 != len { + string.append(&mut space); + } + + OsString::from_vec(string) + }) + } +}