From 4bf376d8029a4af43f912ce2a16e861c742da8aa Mon Sep 17 00:00:00 2001 From: Austen Adler Date: Wed, 9 Mar 2022 20:30:41 -0500 Subject: [PATCH] An unbelieveable amount of changes. too many to count really --- src/kak.rs | 303 +++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 178 +--------------------------- src/math_eval.rs | 4 +- src/shuf.rs | 15 +-- src/sort.rs | 44 +++---- src/uniq.rs | 91 ++++++++------ 6 files changed, 396 insertions(+), 239 deletions(-) create mode 100644 src/kak.rs diff --git a/src/kak.rs b/src/kak.rs new file mode 100644 index 0000000..d58097f --- /dev/null +++ b/src/kak.rs @@ -0,0 +1,303 @@ +use crate::{get_var, KakMessage}; +use std::{ + fmt, + fs::{self, File, OpenOptions}, + io::{BufWriter, Write}, + str::FromStr, +}; + +pub type Selection = String; + +#[derive(PartialEq, Eq, Debug)] +pub struct SelectionWithDesc { + pub content: Selection, + pub desc: SelectionDesc, +} + +#[derive(PartialEq, PartialOrd, Ord, Eq, Debug)] +pub struct SelectionDesc { + pub left: AnchorPosition, + pub right: AnchorPosition, +} + +impl SelectionDesc { + #[must_use] + pub fn sort(&self) -> Self { + if self.left < self.right { + // left anchor is first + Self { + left: self.left.clone(), + right: self.right.clone(), + } + } else { + // right anchor is first + Self { + left: self.right.clone(), + right: self.left.clone(), + } + } + } + + #[must_use] + pub fn contains(&self, b: &Self) -> bool { + // Cursor and anchor can be flipped. Set a.0 to be leftmost cursor + let sorted_a = self.sort(); + let sorted_b = b.sort(); + + sorted_b.left >= sorted_a.left && sorted_b.right <= sorted_a.right + } +} + +impl fmt::Display for SelectionDesc { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{},{}", self.left, self.right) + } +} + +impl FromStr for SelectionDesc { + type Err = KakMessage; + fn from_str(s: &str) -> Result { + let (left, right) = s.split_once(',').ok_or_else(|| { + KakMessage( + "Could not parse position".to_string(), + Some(format!("Could not parse as position: {}", s)), + ) + })?; + + Ok(Self { + left: AnchorPosition::from_str(left)?, + right: AnchorPosition::from_str(right)?, + }) + } +} + +#[derive(PartialOrd, PartialEq, Clone, Eq, Ord, Debug)] +pub struct AnchorPosition { + pub row: usize, + pub col: usize, +} +impl fmt::Display for AnchorPosition { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}.{}", self.row, self.col) + } +} + +impl FromStr for AnchorPosition { + type Err = KakMessage; + fn from_str(s: &str) -> Result { + let (left, right) = s.split_once('.').ok_or_else(|| { + KakMessage( + "Could not parse position".to_string(), + Some(format!("Could not parse as position: {}", s)), + ) + })?; + Ok(Self { + row: usize::from_str(left)?, + col: usize::from_str(right)?, + }) + } +} + +/// # Errors +/// +/// Will return `Err` if command fifo could not be opened, read from, or written to +pub fn get_selections() -> Result, KakMessage> { + response("%val{selections}") +} + +/// # Errors +/// +/// Will return `Err` if command fifo could not be opened, read from, or written to +pub fn get_selections_desc() -> Result, KakMessage> { + response("%val{selections_desc}")? + .iter() + .map(|sd| SelectionDesc::from_str(sd)) + .collect::, KakMessage>>() +} + +/// # Errors +/// +/// Will return `Err` if command fifo could not be opened, read from, or written to, +/// or if `selections.len() != selections_desc.len` +pub fn get_selections_with_desc() -> Result, KakMessage> { + let mut selections = get_selections()?; + let selections_desc = get_selections_desc()?; + + if selections.len() != selections_desc.len() { + return Err(KakMessage( + "Internal error".to_string(), + Some(format!( + "Counts for selections={}, selections_desc={}", + selections.len(), + selections_desc.len() + )), + )); + } + + // Kakoune prints selections in file order, but prints selections_desc rotated based on current selection + let min_selection = selections_desc.iter().min().ok_or_else(|| { + KakMessage( + "Internal error".to_string(), + Some("No selections in selections_desc".to_string()), + ) + })?; + // Need to rotate selections by primary selection's position in the list + match selections_desc.iter().position(|p| p == min_selection) { + Some(i) => { + selections.rotate_right(i); + } + None => { + return Err(KakMessage( + "Internal error".to_string(), + Some(format!( + "Primary selection {} not found in selection_desc list ({:#?})", + min_selection, selections_desc + )), + )) + } + } + + selections + .into_iter() + .zip(selections_desc.into_iter()) + .map(|(content, desc)| Ok(SelectionWithDesc { content, desc })) + .collect::, _>>() +} + +/// # Errors +/// +/// Will return `Err` if command fifo could not be opened, read from, or written to +pub fn set_selections<'a, I, S: 'a>(selections: I) -> Result<(), KakMessage> +where + I: IntoIterator, + S: AsRef + fmt::Display, +{ + let mut f = open_command_fifo()?; + write!(f, "reg '\"'")?; + for i in selections { + write!(f, " '{}'", i.as_ref().replace('\'', "''"))?; + } + write!(f, "; exec R;")?; + f.flush()?; + Ok(()) +} + +/// # Errors +/// +/// Will return `Err` if command fifo could not be opened, read from, or written to +pub fn set_selections_desc<'a, I>(selections: I) -> Result<(), KakMessage> +where + I: IntoIterator, +{ + let mut f = open_command_fifo()?; + write!(f, "select")?; + for i in selections { + write!(f, " {}", i)?; + } + write!(f, ";")?; + f.flush()?; + Ok(()) +} + +/// # Errors +/// +/// Will return `Err` if command fifo could not be opened, read from, or written to +pub fn send_message(msg: &KakMessage) -> Result<(), Box> { + let msg_str = msg.0.replace('\'', "''"); + { + let mut f = + open_command_fifo().map_err(|e| format!("Could not open command fifo: {:?}", e))?; + + write!(f, "echo '{}';", msg_str)?; + write!(f, "echo -debug '{}';", msg_str)?; + + if let Some(debug_msg_str) = &msg.1 { + write!(f, "echo -debug '{}';", debug_msg_str.replace('\'', "''"))?; + } + f.flush()?; + } + Ok(()) +} + +/// # Errors +/// +/// Will return `Err` if command fifo could not be opened or written to +pub fn exec(cmd: &str) -> Result<(), KakMessage> { + let mut f = open_command_fifo()?; + + write!(f, "{}", cmd)?; + f.flush().map_err(Into::into) +} + +/// # Errors +/// +/// Will return `Err` if command fifo could not be opened or written to +pub fn response(msg: &str) -> Result, KakMessage> { + exec(&format!( + "echo -quoting shell -to-file {} -- {msg}", + get_var("kak_response_fifo")? + ))?; + + let selections = shellwords::split(&fs::read_to_string(&get_var("kak_response_fifo")?)?)?; + + Ok(selections) +} + +/// # Errors +/// +/// Will return `Err` if command fifo could not be opened +pub fn open_command_fifo() -> Result, KakMessage> { + OpenOptions::new() + .write(true) + .append(true) + .open(&get_var("kak_command_fifo")?) + .map(BufWriter::new) + .map_err(Into::into) +} + +#[cfg(test)] +mod test { + use super::*; + const SD: SelectionDesc = SelectionDesc { + left: AnchorPosition { row: 18, col: 9 }, + right: AnchorPosition { row: 10, col: 1 }, + }; + #[test] + fn test_anchor_position() { + // Check parsing + assert_eq!(SelectionDesc::from_str("18.9,10.1").unwrap(), SD); + // Check if multiple parsed ones match + assert_eq!( + SelectionDesc::from_str("18.9,10.1").unwrap(), + SelectionDesc::from_str("18.9,10.1").unwrap() + ); + } + + #[test] + fn test_sort() { + // Check if sorting works + assert_eq!(SD.sort(), SelectionDesc::from_str("10.1,18.9").unwrap()); + assert_eq!(SD.sort(), SD.sort().sort()); + } + + #[test] + fn test_contains() { + assert!(SD.contains(&SD)); + assert!(SD.contains(&SelectionDesc::from_str("17.9,10.1").unwrap())); + assert!(SD.contains(&SelectionDesc::from_str("18.8,10.1").unwrap())); + assert!(SD.contains(&SelectionDesc::from_str("18.9,11.1").unwrap())); + assert!(SD.contains(&SelectionDesc::from_str("18.9,10.2").unwrap())); + assert!(SD.contains(&SelectionDesc::from_str("10.1,17.9").unwrap())); + assert!(SD.contains(&SelectionDesc::from_str("10.1,18.8").unwrap())); + assert!(SD.contains(&SelectionDesc::from_str("11.1,18.9").unwrap())); + assert!(SD.contains(&SelectionDesc::from_str("10.2,18.9").unwrap())); + assert!(!SD.contains(&SelectionDesc::from_str("19.9,10.1").unwrap())); + assert!(!SD.contains(&SelectionDesc::from_str("18.10,10.1").unwrap())); + assert!(!SD.contains(&SelectionDesc::from_str("18.9,9.1").unwrap())); + assert!(!SD.contains(&SelectionDesc::from_str("18.9,10.0").unwrap())); + assert!(!SD.contains(&SelectionDesc::from_str("10.1,19.9").unwrap())); + assert!(!SD.contains(&SelectionDesc::from_str("10.1,18.10").unwrap())); + assert!(!SD.contains(&SelectionDesc::from_str("9.1,18.9").unwrap())); + assert!(!SD.contains(&SelectionDesc::from_str("10.0,18.9").unwrap())); + } +} diff --git a/src/main.rs b/src/main.rs index 3c53b6d..8ac75af 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,19 +10,15 @@ // #![allow(dead_code, unused_imports)] mod errors; +mod kak; mod math_eval; mod shuf; -mod sort; +// mod sort; mod uniq; use clap::{Parser, Subcommand}; use errors::KakMessage; -use std::{ - convert::Into, - env, fs, - fs::{File, OpenOptions}, - io::{BufWriter, Write}, - str::FromStr, -}; +pub use kak::*; +use std::env; #[derive(Parser, Debug)] #[clap(about, version, author)] @@ -38,83 +34,13 @@ struct Cli { #[derive(Subcommand, Debug)] enum Commands { - Sort(sort::Options), + // Sort(sort::Options), Shuf(shuf::Options), Uniq(uniq::Options), #[clap(visible_aliases = &["bc", "eval"])] MathEval(math_eval::Options), } -#[derive(PartialEq, PartialOrd, Ord, Eq, Debug)] -pub struct SelectionDesc { - left: AnchorPosition, - right: AnchorPosition, -} - -impl FromStr for SelectionDesc { - type Err = KakMessage; - fn from_str(s: &str) -> Result { - let (left, right) = s.split_once(',').ok_or_else(|| { - KakMessage( - "Could not parse position".to_string(), - Some(format!("Could not parse as position: {}", s)), - ) - })?; - - Ok(Self { - left: AnchorPosition::from_str(left)?, - right: AnchorPosition::from_str(right)?, - }) - } -} - -#[derive(PartialOrd, PartialEq, Clone, Eq, Ord, Debug)] -pub struct AnchorPosition { - row: usize, - col: usize, -} -impl FromStr for AnchorPosition { - type Err = KakMessage; - fn from_str(s: &str) -> Result { - let (left, right) = s.split_once('.').ok_or_else(|| { - KakMessage( - "Could not parse position".to_string(), - Some(format!("Could not parse as position: {}", s)), - ) - })?; - Ok(Self { - row: usize::from_str(left)?, - col: usize::from_str(right)?, - }) - } -} - -impl SelectionDesc { - fn sort(&self) -> Self { - if self.left < self.right { - // left anchor is first - Self { - left: self.left.clone(), - right: self.right.clone(), - } - } else { - // right anchor is first - Self { - left: self.right.clone(), - right: self.left.clone(), - } - } - } - - fn contains(&self, b: &Self) -> bool { - // Cursor and anchor can be flipped. Set a.0 to be leftmost cursor - let sorted_a = self.sort(); - let sorted_b = b.sort(); - - sorted_b.left >= sorted_a.left && sorted_b.right <= sorted_a.right - } -} - fn main() { let msg = match run() { Ok(msg) => msg, @@ -134,23 +60,6 @@ fn main() { } } -fn send_message(msg: &KakMessage) -> Result<(), Box> { - let msg_str = msg.0.replace('\'', "''"); - { - let mut f = - open_command_fifo().map_err(|e| format!("Could not open command fifo: {:?}", e))?; - - write!(f, "echo '{}';", msg_str)?; - write!(f, "echo -debug '{}';", msg_str)?; - - if let Some(debug_msg_str) = &msg.1 { - write!(f, "echo -debug '{}';", debug_msg_str.replace('\'', "''"))?; - } - f.flush()?; - } - Ok(()) -} - fn run() -> Result { let options = Cli::try_parse().map_err(|e| { KakMessage( @@ -160,49 +69,13 @@ fn run() -> Result { })?; match &options.command { - Commands::Sort(o) => sort::sort(o), + // Commands::Sort(o) => sort::sort(o), Commands::Shuf(o) => shuf::shuf(o), Commands::Uniq(o) => uniq::uniq(o), Commands::MathEval(o) => math_eval::math_eval(o), } } -/// # Errors -/// -/// Will return `Err` if command fifo could not be opened or written to -pub fn kak_exec(cmd: &str) -> Result<(), KakMessage> { - let mut f = open_command_fifo()?; - - write!(f, "{}", cmd)?; - f.flush().map_err(Into::into) -} - -/// # Errors -/// -/// Will return `Err` if command fifo could not be opened or written to -pub fn kak_response(msg: &str) -> Result, KakMessage> { - kak_exec(&format!( - "echo -quoting shell -to-file {} -- {msg}", - get_var("kak_response_fifo")? - ))?; - - let selections = shellwords::split(&fs::read_to_string(&get_var("kak_response_fifo")?)?)?; - - Ok(selections) -} - -/// # Errors -/// -/// Will return `Err` if command fifo could not be opened -pub fn open_command_fifo() -> Result, KakMessage> { - OpenOptions::new() - .write(true) - .append(true) - .open(&get_var("kak_command_fifo")?) - .map(BufWriter::new) - .map_err(Into::into) -} - /// # Errors /// /// Will return `Err` if requested environment variable is not unicode or not present @@ -216,42 +89,3 @@ pub fn get_var(var_name: &str) -> Result { } }) } - -#[cfg(test)] -mod test { - use super::*; - const sd: SelectionDesc = SelectionDesc { - left: AnchorPosition { row: 18, col: 9 }, - right: AnchorPosition { row: 10, col: 1 }, - }; - #[test] - fn test_anchor_position() { - // let sd = SelectionDesc { - // left: AnchorPosition { row: 18, col: 9 }, - // right: AnchorPosition { row: 10, col: 0 }, - // }; - // Check parsing - assert_eq!(SelectionDesc::from_str("18.9,10.1").unwrap(), sd); - // Check if multiple parsed ones match - assert_eq!( - SelectionDesc::from_str("18.9,10.1").unwrap(), - SelectionDesc::from_str("18.9,10.1").unwrap() - ); - // Check if sorting works - assert_eq!(sd.sort(), SelectionDesc::from_str("10.1,18.9").unwrap()); - assert_eq!(sd.sort(), sd.sort().sort()); - } - - #[test] - fn test_contains() { - assert_true!(sd.contains(sd)); - assert_false!(sd.contains(SelectionDesc::from_str("17.9,10.1").unwrap())); - assert_false!(sd.contains(SelectionDesc::from_str("18.8,10.1").unwrap())); - assert_false!(sd.contains(SelectionDesc::from_str("18.9,11.1").unwrap())); - assert_false!(sd.contains(SelectionDesc::from_str("18.9,10.2").unwrap())); - assert_true!(sd.contains(SelectionDesc::from_str("19.9,10.1").unwrap())); - assert_true!(sd.contains(SelectionDesc::from_str("18.10,10.1").unwrap())); - assert_true!(sd.contains(SelectionDesc::from_str("18.9,9.1").unwrap())); - assert_true!(sd.contains(SelectionDesc::from_str("18.9,10.0").unwrap())); - } -} diff --git a/src/math_eval.rs b/src/math_eval.rs index 6299f0f..9f07a1c 100644 --- a/src/math_eval.rs +++ b/src/math_eval.rs @@ -1,11 +1,11 @@ -use crate::{kak_response, open_command_fifo, KakMessage}; +use crate::{get_selections, open_command_fifo, KakMessage}; use evalexpr::{eval, Value}; use std::io::Write; #[derive(clap::StructOpt, Debug)] pub struct Options; pub fn math_eval(_options: &Options) -> Result { - let selections = kak_response("%val{selections}")?; + let selections = get_selections()?; let mut f = open_command_fifo()?; write!(f, "reg '\"'")?; diff --git a/src/shuf.rs b/src/shuf.rs index 7286546..0339d80 100644 --- a/src/shuf.rs +++ b/src/shuf.rs @@ -1,23 +1,14 @@ -use crate::{kak_response, open_command_fifo, KakMessage}; +use crate::{get_selections, set_selections, KakMessage}; use rand::{seq::SliceRandom, thread_rng}; -use std::io::Write; #[derive(clap::StructOpt, Debug)] pub struct Options; pub fn shuf(_options: &Options) -> Result { - let mut selections = kak_response("%val{selections}")?; + let mut selections = get_selections()?; let mut rng = thread_rng(); selections.shuffle(&mut rng); - let mut f = open_command_fifo()?; - write!(f, "reg '\"'")?; - - for i in &selections { - let new_selection = i.replace('\'', "''"); - write!(f, " '{}'", new_selection)?; - } - write!(f, " ; exec R;")?; - f.flush()?; + set_selections(selections.iter())?; Ok(KakMessage( format!("Shuf {} selections", selections.len()), diff --git a/src/sort.rs b/src/sort.rs index d2db957..d953580 100644 --- a/src/sort.rs +++ b/src/sort.rs @@ -1,4 +1,6 @@ -use crate::{kak_response, open_command_fifo, KakMessage, SelectionDesc}; +use crate::{ + get_selections_with_desc, kak, open_command_fifo, KakMessage, SelectionDesc, SelectionWithDesc, +}; use alphanumeric_sort::compare_str; use regex::Regex; use std::{cmp::Ordering, io::Write, str::FromStr}; @@ -41,17 +43,9 @@ struct SortableSelection<'a> { /// Gets a Vec of sortable selections with a given list of subselections and descriptions fn get_sortable_selections_subselections<'a, 'b, 'tmp, S: AsRef + std::fmt::Debug + 'a>( options: &'b Options, - selections: &'a [S], - selections_desc: &'tmp [S], - subselections: &'a [S], - subselections_desc: &'tmp [S], + selections: Vec, + subselections: Vec, ) -> Result>, KakMessage> { - if selections.len() != selections_desc.len() || subselections.len() != subselections_desc.len() - { - return Err(KakMessage ("Internal error".to_string(), Some( - format!("Counts for selections={}, selections_desc={}, subselections={}, subselections_desc={}",selections.len(),selections_desc.len(),subselections.len(),subselections_desc.len()) - ))); - } let mut sortable_selections = selections .iter() .zip(selections_desc.iter()) @@ -126,20 +120,30 @@ fn to_sortable_selection<'a, 'b>( pub fn sort(options: &Options) -> Result { eprintln!("Got sort options: {:?}", options); - let subselections: Option<(Vec, Vec)> = options + let subselections = options .subselections_register - .map::, Vec), KakMessage>, _>(|_c| { - let subselections = kak_response("%val{selections}")?; - let subselections_desc = kak_response("%val{selections_desc}")?; - // kak_exec(&format!("exec z",))?; - Ok((subselections, subselections_desc)) + .map(|_r| -> Result, KakMessage> { + let ret = get_selections_with_desc()?; + kak::exec("exec z")?; + Ok(ret) }) .transpose()?; - let selections = kak_response("%val{selections}")?; + let selections = get_selections_with_desc()?; + + // let subselections: Option<(Vec, Vec)> = options + // .subselections_register + // .map::, Vec), KakMessage>, _>(|_c| { + // let subselections = kak::response("%val{selections}")?; + // let subselections_desc = kak::response("%val{selections_desc}")?; + // // kak_exec(&format!("exec z",))?; + // Ok((subselections, subselections_desc)) + // }) + // .transpose()?; + // let selections = kak::response("%val{selections}")?; let mut zipped: Vec> = match subselections { - Some((ref subselections, ref subselections_desc)) => { - let selections_desc = kak_response("%val{selections_desc}")?; + Some(subselections) => { + let selections_desc = kak::response("%val{selections_desc}")?; get_sortable_selections_subselections( options, diff --git a/src/uniq.rs b/src/uniq.rs index 384ad30..ed2fa36 100644 --- a/src/uniq.rs +++ b/src/uniq.rs @@ -1,8 +1,9 @@ -use crate::{kak_response, open_command_fifo, KakMessage}; +use crate::{ + get_selections_desc, set_selections, set_selections_desc, KakMessage, SelectionWithDesc, +}; use std::{ collections::{hash_map::DefaultHasher, BTreeSet}, hash::{Hash, Hasher}, - io::Write, }; #[derive(clap::StructOpt, Debug)] pub struct Options { @@ -13,44 +14,68 @@ pub struct Options { no_skip_whitespace: bool, } pub fn uniq(options: &Options) -> Result { - let selections = kak_response("%val{selections}")?; + let mut selections = crate::get_selections_with_desc()?; + // Sort selections so the first element is the unique one, not an arbitrary one based on primary selection + selections.sort_by_key(|s| s.desc.sort()); - let mut f = open_command_fifo()?; - write!(f, "reg '\"'")?; + // Set the new selection types + let new_selections: Vec> = selections + .into_iter() + // Create a BTreeSet of hashes of lines. This way, string content is not stored, but uniqueness can be determined + .scan(BTreeSet::new(), |state, s| { + let key = if options.no_skip_whitespace { + s.content.as_str() + } else { + s.content.trim() + }; - for i in selections.iter().scan(BTreeSet::new(), |state, s| { - let key = if options.no_skip_whitespace { - s - } else { - s.trim() - }; + let key = if options.ignore_case { + key.to_lowercase() + } else { + // TODO: Do I really need to clone this? + key.to_string() + }; - 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); - let mut hasher = DefaultHasher::new(); - key.hash(&mut hasher); - - Some(if state.insert(hasher.finish()) { - // True if this is a new line - s - } else { - // Nothing was inserted because we already saw this line - "" + // 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) + } }) - }) { - let new_selection = i.replace('\'', "''"); - write!(f, " '{}'", new_selection)?; - } - write!(f, " ; exec R;")?; - f.flush()?; + .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!("Uniq {} selections", selections.len()), + format!("{} unique selections out of {}", new_count, old_count), None, )) }