diff --git a/kakplugin/src/errors.rs b/kakplugin/src/errors.rs index accbf11..5d4295d 100644 --- a/kakplugin/src/errors.rs +++ b/kakplugin/src/errors.rs @@ -1,4 +1,4 @@ -use std::num::ParseIntError; +use std::{fmt, fmt::Display, num::ParseIntError}; #[derive(Debug)] pub enum KakError { @@ -16,10 +16,14 @@ pub enum KakError { NotImplemented(&'static str), /// Custom error string Custom(String), + /// Custom static error string + CustomStatic(&'static str), /// The selections/selections_desc list passed was empty SetEmptySelections, } +impl std::error::Error for KakError {} + impl KakError { pub fn details(&self) -> String { match self { @@ -30,6 +34,7 @@ impl KakError { Self::Io(e) => format!("{e:?}"), Self::NotImplemented(e) => e.to_string(), Self::Custom(s) => s.clone(), + Self::CustomStatic(s) => s.to_string(), Self::SetEmptySelections => { String::from("Attempted to set selections/selections_desc to empty list") } @@ -37,22 +42,23 @@ impl KakError { } } -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, - Self::SetEmptySelections => - "Attempted to set selections/selections_desc to empty list", - } - ) +impl Display for KakError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Error: ")?; + match self { + Self::EnvVarNotSet(_) => write!(f, "env var not set"), + Self::EnvVarUnicode(_) => write!(f, "env var not unicode"), + Self::Parse(_) => write!(f, "Could not parse"), + Self::KakResponse(_) => write!(f, "Invalid kak response"), + Self::Io(_) => write!(f, "IO error"), + Self::NotImplemented(_) => write!(f, "Not Implemented"), + Self::Custom(s) => write!(f, "{}", s), + Self::CustomStatic(s) => write!(f, "{}", s), + Self::SetEmptySelections => write!( + f, + "Attempted to set selections/selections_desc to empty list" + ), + } } } diff --git a/kakplugin/src/types.rs b/kakplugin/src/types.rs index fd58ad4..b433bb5 100644 --- a/kakplugin/src/types.rs +++ b/kakplugin/src/types.rs @@ -325,7 +325,7 @@ impl FromStr for AnchorPosition { } } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Copy, Clone)] pub enum Register { Numeric0, Numeric1, diff --git a/src/incr.rs b/src/incr.rs index 96abcc7..6215927 100644 --- a/src/incr.rs +++ b/src/incr.rs @@ -40,11 +40,12 @@ pub fn incr(options: &Options, should_increment: bool) -> Result Result { // Commands::Xargs(o) => xargs::xargs(o), Commands::Stdin(o) => stdin::stdin(o), Commands::Box_(o) => box_::box_(o), + Commands::Xlookup(o) => xlookup::xlookup(o), Commands::Incr(o) => incr::incr(o, true), Commands::Decr(o) => incr::incr(o, false), } diff --git a/src/math_eval.rs b/src/math_eval.rs index 8d82000..5beb041 100644 --- a/src/math_eval.rs +++ b/src/math_eval.rs @@ -25,9 +25,10 @@ pub fn math_eval(_options: &Options) -> Result { format!("Processed {} selections", selections.len()) } else { format!( - "Processed {} selections ({} errors)", + "Processed {} selections ({} error{})", selections.len().saturating_sub(err_count), - err_count + err_count, + if err_count == 1 { "" } else { "s" } ) }) } diff --git a/src/xlookup.rs b/src/xlookup.rs new file mode 100644 index 0000000..2d2daf9 --- /dev/null +++ b/src/xlookup.rs @@ -0,0 +1,75 @@ +use crate::utils::get_hash; +use evalexpr::{eval, Value}; +use kakplugin::{ + get_selections, open_command_fifo, set_selections, types::Register, KakError, Selection, +}; +use std::{ + collections::{ + btree_map::Entry::{Occupied, Vacant}, + hash_map::DefaultHasher, + BTreeMap, + }, + hash::{Hash, Hasher}, + io::Write, +}; + +#[derive(clap::StructOpt, Debug)] +pub struct Options { + #[clap(help = "Register with the lookup table")] + register: Register, +} +pub fn xlookup(options: &Options) -> Result { + let lookup_table = build_lookuptable(options.register)?; + eprintln!("Lookup table: {lookup_table:#?}"); + + let selections = get_selections(None)?; + + let mut err_count: usize = 0; + + set_selections(selections.iter().map(|key| { + match lookup_table.get(&get_hash(&key, false, None, false)) { + Some(v) => v.to_string(), + None => { + eprintln!( + "Nothing for '{key}' ({})", + get_hash(&key, false, None, false) + ); + err_count += 1; + String::from("") + } + } + }))?; + + Ok(if err_count == 0 { + format!("Xlookup {} selections", selections.len()) + } else { + format!( + "Xlookup {} selections ({} error{})", + selections.len().saturating_sub(err_count), + err_count, + if err_count == 1 { "" } else { "s" } + ) + }) +} + +pub fn build_lookuptable(reg: Register) -> Result, KakError> { + let mut selections = get_selections(Some(&format!("\"{reg}z")))?; + let mut iter = selections.array_chunks_mut(); + let ret = iter.try_fold(BTreeMap::new(), |mut acc, [key, value]| { + match acc.entry(get_hash(&key, false, None, false)) { + Occupied(_) => Err(KakError::Custom(format!("Duplicate key '{key}'"))), + Vacant(v) => { + v.insert(value.to_owned()); + Ok(acc) + } + } + })?; + + if !iter.into_remainder().is_empty() { + Err(KakError::CustomStatic("Odd number of selections")) + } else if ret.is_empty() { + Err(KakError::CustomStatic("No selections")) + } else { + Ok(ret) + } +}