diff --git a/kakplugin/src/errors.rs b/kakplugin/src/errors.rs index d00d0b7..f716401 100644 --- a/kakplugin/src/errors.rs +++ b/kakplugin/src/errors.rs @@ -12,7 +12,43 @@ pub enum KakError { KakResponse(String), /// IO Error Io(std::io::Error), + /// Not yet implemented + NotImplemented(&'static str), + /// Custom error string + Custom(String), } + +impl KakError { + pub fn details(&self) -> String { + match self { + Self::EnvVarNotSet(e) => e.clone(), + Self::EnvVarUnicode(e) => e.clone(), + Self::Parse(e) => e.clone(), + Self::KakResponse(e) => e.clone(), + Self::Io(e) => format!("{e:?}"), + Self::NotImplemented(e) => e.to_string(), + Self::Custom(s) => s.clone(), + } + } +} + +impl ToString for KakError { + fn to_string(&self) -> String { + format!( + "Error: {}", + match self { + Self::EnvVarNotSet(_) => "env var not set", + Self::EnvVarUnicode(_) => "env var not unicode", + Self::Parse(_) => "Could not parse", + Self::KakResponse(_) => "Invalid kak response", + Self::Io(_) => "IO error", + Self::NotImplemented(_) => "Not Implemented", + Self::Custom(s) => &s, + } + ) + } +} + impl From for KakError { fn from(e: std::io::Error) -> Self { Self::Io(e) diff --git a/kakplugin/src/lib.rs b/kakplugin/src/lib.rs index ebb9867..ee34610 100644 --- a/kakplugin/src/lib.rs +++ b/kakplugin/src/lib.rs @@ -127,7 +127,10 @@ where /// # Errors /// /// Will return `Err` if command fifo could not be opened, read from, or written to -pub fn send_message>(message: S, debug_message: Option) -> Result<(), KakError> { +pub fn display_message>( + message: S, + debug_message: Option, +) -> Result<(), KakError> { let msg_str = message.as_ref().replace('\'', "''"); { let mut f = open_command_fifo()?; @@ -186,7 +189,7 @@ pub fn open_command_fifo() -> Result, KakError> { /// # Errors /// /// Will return `Err` if requested environment variable is not unicode or not present -fn get_var(var_name: &str) -> Result { +pub fn get_var(var_name: &str) -> Result { env::var(var_name).map_err(|e| match e { env::VarError::NotPresent => { KakError::EnvVarNotSet(format!("Env var {} is not defined", var_name)) diff --git a/src/kak.rs b/src/kak.rs deleted file mode 100644 index cabfc40..0000000 --- a/src/kak.rs +++ /dev/null @@ -1,333 +0,0 @@ -use crate::{get_var, KakMessage}; -// use shellwords::ShellWordsIterator; -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, Eq, Debug)] -pub struct SelectionWithSubselections { - pub selection: SelectionWithDesc, - pub subselections: Vec, -} - -#[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>>() -} - -// pub fn get_selections_with_subselections( -// register: &str, -// ) -> Result, KakMessage> { -// // TODO: Escape register -// let subselections = get_selections_with_desc()?; -// exec(format!("\"{}z", register.replace('\'', "''")))?; -// let selections = get_selections_with_desc()?; - -// for sel in selections { -// for i in subselections {} -// } -// } -/// # 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 + ?Sized>(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) -} - -// 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 -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 71ea843..a4dc236 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,9 +6,6 @@ #![allow(clippy::multiple_crate_versions)] #![allow(clippy::struct_excessive_bools)] -// #![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)] -// #![allow(dead_code, unused_imports)] - mod errors; mod kak; mod math_eval; @@ -19,9 +16,8 @@ mod trim; mod uniq; mod xargs; use clap::{Parser, Subcommand}; -use errors::KakMessage; pub use kak::*; -use std::env; +use kakplugin::{display_message, get_var, KakError}; #[derive(Parser, Debug)] #[clap(about, version, author)] @@ -55,31 +51,22 @@ fn main() { panic!("Environment variable kak_command_fifo and kak_response_fifo must be set"); } - let msg = match run() { - Ok(msg) => msg, - Err(msg) => { - // TODO: Do not do a string allocation here - eprintln!( - "{} (Debug info: {})", - msg.0, - msg.1.as_ref().map_or("", String::as_str) - ); - msg - } + let (msg, msg_details) = match run() { + Ok(msg) => (msg, None), + Err(e) => (e.to_string(), Some(e.details())), }; - if let Err(e) = send_message(&msg) { - println!("{}", e); + if let Err(display_error) = display_message(&msg, msg_details.as_ref()) { + // If there was an error sending the display message to kakoune, print it out + eprintln!( + "Error sending message '{msg:?}' (details: '{msg_details:?}') to kak: {display_error:?}" + ); } } -fn run() -> Result { - let options = Cli::try_parse().map_err(|e| { - KakMessage( - "Error parsing arguments".to_string(), - Some(format!("Could not parse: {}", e)), - ) - })?; +fn run() -> Result { + let options = + Cli::try_parse().map_err(|e| KakError::Parse(format!("Argument parse error: {e}")))?; match &options.command { Commands::Sort(o) => sort::sort(o), @@ -91,17 +78,3 @@ fn run() -> Result { Commands::Stdin(o) => stdin::stdin(o), } } - -/// # Errors -/// -/// Will return `Err` if requested environment variable is not unicode or not present -pub fn get_var(var_name: &str) -> Result { - env::var(var_name).map_err(|e| match e { - env::VarError::NotPresent => { - KakMessage(format!("Env var {} is not defined", var_name), None) - } - env::VarError::NotUnicode(_) => { - KakMessage(format!("Env var {} is not unicode", var_name), None) - } - }) -} diff --git a/src/math_eval.rs b/src/math_eval.rs index 9f07a1c..1cc3f94 100644 --- a/src/math_eval.rs +++ b/src/math_eval.rs @@ -1,10 +1,10 @@ -use crate::{get_selections, open_command_fifo, KakMessage}; use evalexpr::{eval, Value}; +use kakplugin::{get_selections, open_command_fifo, KakError}; use std::io::Write; #[derive(clap::StructOpt, Debug)] pub struct Options; -pub fn math_eval(_options: &Options) -> Result { +pub fn math_eval(_options: &Options) -> Result { let selections = get_selections()?; let mut f = open_command_fifo()?; @@ -38,8 +38,5 @@ pub fn math_eval(_options: &Options) -> Result { write!(f, " ; exec R;")?; f.flush()?; - Ok(KakMessage( - format!("MathEval {} selections", selections.len()), - None, - )) + Ok(format!("MathEval {} selections", selections.len())) } diff --git a/src/shuf.rs b/src/shuf.rs index 0339d80..e34475f 100644 --- a/src/shuf.rs +++ b/src/shuf.rs @@ -1,8 +1,8 @@ -use crate::{get_selections, set_selections, KakMessage}; +use kakplugin::{get_selections, set_selections, KakError}; use rand::{seq::SliceRandom, thread_rng}; #[derive(clap::StructOpt, Debug)] pub struct Options; -pub fn shuf(_options: &Options) -> Result { +pub fn shuf(_options: &Options) -> Result { let mut selections = get_selections()?; let mut rng = thread_rng(); @@ -10,8 +10,5 @@ pub fn shuf(_options: &Options) -> Result { set_selections(selections.iter())?; - Ok(KakMessage( - format!("Shuf {} selections", selections.len()), - None, - )) + Ok(format!("Shuf {} selections", selections.len())) } diff --git a/src/sort.rs b/src/sort.rs index 257ef01..bcf0498 100644 --- a/src/sort.rs +++ b/src/sort.rs @@ -1,5 +1,5 @@ -use crate::{get_selections_with_desc, kak, open_command_fifo, KakMessage, SelectionWithDesc}; use alphanumeric_sort::compare_str; +use kakplugin::{self, get_selections_with_desc, open_command_fifo, KakError, SelectionWithDesc}; use regex::Regex; use std::{cmp::Ordering, io::Write}; @@ -117,15 +117,15 @@ fn to_sortable_selection<'a, 'b>( } } -pub fn sort(options: &Options) -> Result { +pub fn sort(options: &Options) -> Result { // subselections is Some if the user requests it in subselections_register // It will "exec z" to restore the selections before setting selections // If subselections is None, "exec z" is not called let subselections: Option> = options .subselections_register - .map::, _>(|c| { + .map::, _>(|c| { let subselections = get_selections_with_desc()?; - kak::exec(&format!("exec {}", c))?; + kakplugin::exec(&format!("exec {}", c))?; Ok(subselections) }) .transpose()?; @@ -133,9 +133,9 @@ pub fn sort(options: &Options) -> Result { let mut zipped: Vec> = match (&options.regex, &subselections) { (Some(_), Some(_)) => { - return Err("Cannot pass regex and subselections register" - .to_string() - .into()) + return Err(KakError::Custom( + "Cannot pass regex and subselections register".to_string(), + )) } (None, None) => { // Do a regular sort on the content @@ -166,9 +166,8 @@ pub fn sort(options: &Options) -> Result { } (None, _) => { // Sort based on subselections - return Err(KakMessage( - "Not yet implemented".to_string(), - Some("Sort by subselection is not yet implemented".to_string()), + return Err(KakError::NotImplemented( + "Sort by subselection is not yet implemented", )); } }; @@ -212,8 +211,5 @@ pub fn sort(options: &Options) -> Result { write!(f, " ; exec R;")?; f.flush()?; - Ok(KakMessage( - format!("Sorted {} selections", zipped.len()), - None, - )) + Ok(format!("Sorted {} selections", zipped.len())) } diff --git a/src/stdin.rs b/src/stdin.rs index 4c2c389..61868a4 100644 --- a/src/stdin.rs +++ b/src/stdin.rs @@ -1,4 +1,4 @@ -use crate::{get_selections_with_desc, set_selections, KakMessage}; +use kakplugin::{get_selections_with_desc, set_selections, KakError}; use std::{ io::{BufRead, BufReader, Write}, process::{Command, Stdio}, @@ -8,7 +8,7 @@ pub struct Options { command: String, args: Vec, } -pub fn stdin(options: &Options) -> Result { +pub fn stdin(options: &Options) -> Result { let mut child = Command::new(&options.command) .args(&options.args) .stdin(Stdio::piped()) @@ -17,7 +17,7 @@ pub fn stdin(options: &Options) -> Result { .expect("Failed to spawn child process"); let mut child_stdin = child.stdin.take().expect("Failed to open stdin"); - let handle = std::thread::spawn(move || -> Result<(), KakMessage> { + let handle = std::thread::spawn(move || -> Result<(), KakError> { for s in get_selections_with_desc()? { eprintln!("Got selection {}", s.content); write!(child_stdin, "{}\0", s.content)?; @@ -29,7 +29,7 @@ pub fn stdin(options: &Options) -> Result { BufReader::new(child.stdout.take().expect("Failed to get stdout")) .split(b'\0') .map(|s| Ok(String::from_utf8_lossy(&s?).into_owned())) - .collect::, KakMessage>>()? + .collect::, KakError>>()? .iter(), )?; @@ -37,7 +37,7 @@ pub fn stdin(options: &Options) -> Result { // TODO: Do not use a string handle .join() - .map_err(|_e| String::from("Could not join background process"))??; + .map_err(|_e| KakError::Custom("Could not join background process".to_string()))??; Ok("stdin selections".into()) } diff --git a/src/trim.rs b/src/trim.rs index 75f6d21..4606de4 100644 --- a/src/trim.rs +++ b/src/trim.rs @@ -1,4 +1,4 @@ -use crate::{get_selections, open_command_fifo, KakMessage}; +use kakplugin::{get_selections, open_command_fifo, KakError}; use std::io::Write; #[derive(clap::StructOpt, Debug)] @@ -11,7 +11,7 @@ pub struct Options { no_preserve_newline: bool, } -pub fn trim(options: &Options) -> Result { +pub fn trim(options: &Options) -> Result { let selections = get_selections()?; let mut f = open_command_fifo()?; @@ -45,11 +45,8 @@ pub fn trim(options: &Options) -> Result { write!(f, " ; exec R;")?; f.flush()?; - Ok(KakMessage( - format!( - "Trimmed {} selections ({} changed)", - num_selections, num_trimmed - ), - None, + Ok(format!( + "Trimmed {} selections ({} changed)", + num_selections, num_trimmed )) } diff --git a/src/uniq.rs b/src/uniq.rs index 02a281a..62e0397 100644 --- a/src/uniq.rs +++ b/src/uniq.rs @@ -34,6 +34,7 @@ pub fn uniq(options: &Options) -> Result { s.content.trim() }; + // If they requested a regex match, set the key to the string slice of that match if let Some(regex_match) = (|| { let captures = options.regex.as_ref()?.captures(key)?; captures @@ -67,12 +68,9 @@ pub fn uniq(options: &Options) -> Result { }) .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, + None => "", }))?; // Deselect all `None` strings (all rows that have been seen before) diff --git a/src/xargs.rs b/src/xargs.rs index bb08f68..7858d15 100644 --- a/src/xargs.rs +++ b/src/xargs.rs @@ -1,4 +1,4 @@ -use crate::{get_selections_with_desc, set_selections, KakMessage}; +use kakplugin::{get_selections_with_desc, set_selections, KakError}; use std::{ io::{BufRead, BufReader, Write}, process::{Command, Stdio}, @@ -7,7 +7,7 @@ use std::{ pub struct Options { args: Vec, } -pub fn xargs(options: &Options) -> Result { +pub fn xargs(options: &Options) -> Result { let mut child = Command::new("xargs") .arg("-0") .arg("--") @@ -18,7 +18,7 @@ pub fn xargs(options: &Options) -> Result { .expect("Failed to spawn child process"); let mut stdin = child.stdin.take().expect("Failed to open stdin"); - let handle = std::thread::spawn(move || -> Result<(), KakMessage> { + let handle = std::thread::spawn(move || -> Result<(), KakError> { for s in get_selections_with_desc()? { eprintln!("Got selection {}", s.content); write!(stdin, "{}\0", s.content)?; @@ -29,18 +29,23 @@ pub fn xargs(options: &Options) -> Result { eprintln!("About t oreadvv"); set_selections( - BufReader::new(child.stdout.take().ok_or("Failed to get stdout")?) - .split(b'\0') - .map(|s| Ok(String::from_utf8_lossy(&s?).into_owned())) - .collect::, KakMessage>>()? - .iter(), + BufReader::new( + child + .stdout + .take() + .ok_or(KakError::Custom("Failed to get stdout".to_string()))?, + ) + .split(b'\0') + .map(|s| Ok(String::from_utf8_lossy(&s?).into_owned())) + .collect::, KakError>>()? + .iter(), )?; // Wait for the background process to exit // TODO: Do not use a string handle .join() - .map_err(|_e| "Could not join background process")??; + .map_err(|_e| KakError::Custom("Could not join background process".to_string()))??; Ok("xargs selections".into()) }