diff --git a/Cargo.toml b/Cargo.toml index 4b86a79..048ec85 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ keywords = ["cli", "kakoune"] regex = "1" clap = {version = "3", features = ["derive", "env"]} alphanumeric-sort = "1" +# shellwords = {version = "1", path = "../../../git/rust-shellwords/"} shellwords = "1" rand = "0.8" evalexpr = "7" diff --git a/src/kak.rs b/src/kak.rs index 95c6d05..3448257 100644 --- a/src/kak.rs +++ b/src/kak.rs @@ -1,4 +1,5 @@ use crate::{get_var, KakMessage}; +// use shellwords::ShellWordsIterator; use std::{ fmt, fs::{self, File, OpenOptions}, @@ -261,6 +262,17 @@ pub fn response(msg: &str) -> Result, KakMessage> { Ok(selections) } +// pub fn response_iter(msg: &str) -> Result { +// exec(&format!( +// "echo -quoting shell -to-file {} -- {msg}", +// get_var("kak_response_fifo")? +// ))?; + +// Ok(shellwords::split_iter(&fs::read_to_string(&get_var( +// "kak_response_fifo", +// )?)?)) +// } + /// # Errors /// /// Will return `Err` if command fifo could not be opened diff --git a/src/xargs.rs b/src/xargs.rs new file mode 100644 index 0000000..11108de --- /dev/null +++ b/src/xargs.rs @@ -0,0 +1,102 @@ +use crate::{ + get_selections_desc, set_selections, set_selections_desc, KakMessage, SelectionWithDesc, +}; +use regex::Regex; +use std::{ + collections::{hash_map::DefaultHasher, BTreeSet}, + hash::{Hash, Hasher}, +}; +#[derive(clap::StructOpt, Debug)] +pub struct Options { +} +pub fn xargs(options: &Options) -> Result { + let mut child = Command::new("xargs") + .args(["-0"]) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn().expect("Failed to spawn child process"); + + let mut stdin = child.stdin.take().expect("Failed to open stdin"); + std::thread::spawn(move || { + for s in crate::get_selections_with_desc()? { + stdin.write_all(s.selection).expect("Failed to write to stdin"); + stdin.write_all('\0').expect("Failed to write to stdin"); + } + + }) + // Sort selections so the first element is the xargsue one, not an arbitrary one based on primary selection + selections.sort_by_key(|s| s.desc.sort()); + + // Set the new selection types + let new_selections: Vec> = selections + .into_iter() + // Create a BTreeSet of hashes of selections. This way, string content is not stored, but xargsueness can be determined + .scan(BTreeSet::new(), |state, s| { + // Strip whitespace if requested + let mut key = if options.no_skip_whitespace { + s.content.as_str() + } else { + s.content.trim() + }; + + if let Some(regex_match) = (|| { + let captures = options.regex.as_ref()?.captures(key)?; + captures + .get(1) + .or_else(|| captures.get(0)) + .map(|m| m.as_str()) + })() { + key = regex_match; + } + + // Ignore case if requested + let key = if options.ignore_case { + key.to_lowercase() + } else { + // TODO: Do I really need to clone this? + key.to_string() + }; + + let mut hasher = DefaultHasher::new(); + key.hash(&mut hasher); + + // Try inserting to the hash + if state.insert(hasher.finish()) { + // True if this is a string we haven't seen before + Some(Some(s)) + } else { + // Nothing was inserted because we already saw this string + // Return Some(None) so the iterator can continue + Some(None) + } + }) + .collect(); + + // Preallocate so the content and string have the same type, but allocation is not repeated + // TODO: Do we really have to do this? + let empty_string = String::default(); + set_selections(new_selections.iter().map(|i| match i { + Some(s) => &s.content, + None => &empty_string, + }))?; + + // Deselect all `None` strings (all rows that have been seen before) + let mut new_selections_desc = get_selections_desc()?; + new_selections_desc.sort(); + set_selections_desc( + // Refresh seelections_desc because positions have changed + new_selections_desc + .iter() + .zip(new_selections.iter()) + // If the string was emptied (None), then do not set `sd` + .filter_map(|(sd, s)| if s.is_some() { Some(sd) } else { None }), + )?; + + let old_count = new_selections.len(); + let new_count = new_selections.iter().flatten().count(); + + Ok(KakMessage( + format!("{} xargs selections out of {}", new_count, old_count), + None, + )) +}