Add set command

This commit is contained in:
Austen Adler 2022-06-06 18:01:49 -04:00
parent ec3c685a16
commit 01a7063c22
10 changed files with 784 additions and 58 deletions

17
Cargo.lock generated
View File

@ -139,6 +139,8 @@ dependencies = [
"clap",
"evalexpr",
"kakplugin",
"linked-hash-map",
"linked_hash_set",
"rand",
"regex",
"shellwords",
@ -156,6 +158,21 @@ version = "0.2.113"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eef78b64d87775463c549fbd80e19249ef436ea3bf1de2a1eb7e717ec7fab1e9"
[[package]]
name = "linked-hash-map"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
[[package]]
name = "linked_hash_set"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47186c6da4d81ca383c7c47c1bfc80f4b95f4720514d860a5407aaf4233f9588"
dependencies = [
"linked-hash-map",
]
[[package]]
name = "memchr"
version = "2.4.1"

View File

@ -39,6 +39,8 @@ shellwords = "1"
rand = "0.8"
evalexpr = "7"
kakplugin = {path = "./kakplugin/"}
linked-hash-map = "0.5.4"
linked_hash_set = "0.1.4"
[profile.release]
lto = true

View File

@ -1,12 +1,13 @@
mod errors;
mod types;
pub mod types;
pub use errors::KakError;
use std::{
env, fmt,
env,
fs::{self, File, OpenOptions},
io::{BufWriter, Write},
str::FromStr,
};
use types::Register;
pub use types::{
AnchorPosition, Selection, SelectionDesc, SelectionWithDesc, SelectionWithSubselections,
};
@ -95,14 +96,14 @@ pub fn get_selections_with_desc() -> Result<Vec<SelectionWithDesc>, KakError> {
pub fn set_selections<'a, I, S: 'a + ?Sized>(selections: I) -> Result<(), KakError>
where
I: IntoIterator<Item = &'a S>,
S: AsRef<str> + fmt::Display,
S: AsRef<str>,
{
let mut f = open_command_fifo()?;
write!(f, "reg '\"'")?;
write!(f, "set-register '\"'")?;
for i in selections {
write!(f, " '{}'", i.as_ref().replace('\'', "''"))?;
write!(f, " '{}'", escape(i))?;
}
write!(f, "; exec R;")?;
write!(f, "; execute-keys R;")?;
f.flush()?;
Ok(())
}
@ -131,7 +132,7 @@ pub fn display_message<S: AsRef<str>>(
message: S,
debug_message: Option<S>,
) -> Result<(), KakError> {
let msg_str = message.as_ref().replace('\'', "''");
let msg_str = escape(message);
{
let mut f = open_command_fifo()?;
@ -139,32 +140,36 @@ pub fn display_message<S: AsRef<str>>(
write!(f, "echo -debug '{}';", msg_str)?;
if let Some(debug_msg_str) = &debug_message.as_ref() {
write!(
f,
"echo -debug '{}';",
debug_msg_str.as_ref().replace('\'', "''")
)?;
write!(f, "echo -debug '{}';", escape(debug_msg_str))?;
}
f.flush()?;
}
Ok(())
}
pub fn escape<S: AsRef<str>>(s: S) -> String {
s.as_ref().replace('\'', "''")
}
/// # Errors
///
/// Will return `Err` if command fifo could not be opened or written to
pub fn exec(cmd: &str) -> Result<(), KakError> {
pub fn cmd(cmd: &str) -> Result<(), KakError> {
let mut f = open_command_fifo()?;
write!(f, "{}", cmd)?;
write!(f, "{};", cmd)?;
f.flush().map_err(Into::into)
}
pub fn restore_register(r: &Register) -> Result<(), KakError> {
cmd(&format!("execute-keys '\"{}z'", r.kak_escaped()))
}
/// # Errors
///
/// Will return `Err` if command fifo could not be opened or written to
pub fn response(msg: &str) -> Result<Vec<String>, KakError> {
exec(&format!(
cmd(&format!(
"echo -quoting shell -to-file {} -- {msg}",
get_var("kak_response_fifo")?
))?;

View File

@ -93,6 +93,352 @@ impl FromStr for AnchorPosition {
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum Register {
Numeric0,
Numeric1,
Numeric2,
Numeric3,
Numeric4,
Numeric5,
Numeric6,
Numeric7,
Numeric8,
Numeric9,
UppercaseA,
UppercaseB,
UppercaseC,
UppercaseD,
UppercaseE,
UppercaseF,
UppercaseG,
UppercaseH,
UppercaseI,
UppercaseJ,
UppercaseK,
UppercaseL,
UppercaseM,
UppercaseN,
UppercaseO,
UppercaseP,
UppercaseQ,
UppercaseR,
UppercaseS,
UppercaseT,
UppercaseU,
UppercaseV,
UppercaseW,
UppercaseX,
UppercaseY,
UppercaseZ,
LowercaseA,
LowercaseB,
LowercaseC,
LowercaseD,
LowercaseE,
LowercaseF,
LowercaseG,
LowercaseH,
LowercaseI,
LowercaseJ,
LowercaseK,
LowercaseL,
LowercaseM,
LowercaseN,
LowercaseO,
LowercaseP,
LowercaseQ,
LowercaseR,
LowercaseS,
LowercaseT,
LowercaseU,
LowercaseV,
LowercaseW,
LowercaseX,
LowercaseY,
LowercaseZ,
Dquote,
Slash,
Arobase,
Caret,
Pipe,
Percent,
Dot,
Hash,
Underscore,
Colon,
}
impl Register {
pub fn kak_expanded(&self) -> &'static str {
match self {
Self::Dquote => "dquote",
Self::Slash => "slash",
Self::Arobase => "arobase",
Self::Caret => "caret",
Self::Pipe => "pipe",
Self::Percent => "percent",
Self::Dot => "dot",
Self::Hash => "hash",
Self::Underscore => "underscore",
Self::Colon => "colon",
// Everything else is the same
_ => self.kak_escaped(),
}
}
pub fn to_char(&self) -> char {
match self {
Self::Numeric0 => '0',
Self::Numeric1 => '1',
Self::Numeric2 => '2',
Self::Numeric3 => '3',
Self::Numeric4 => '4',
Self::Numeric5 => '5',
Self::Numeric6 => '6',
Self::Numeric7 => '7',
Self::Numeric8 => '8',
Self::Numeric9 => '9',
Self::UppercaseA => 'A',
Self::UppercaseB => 'B',
Self::UppercaseC => 'C',
Self::UppercaseD => 'D',
Self::UppercaseE => 'E',
Self::UppercaseF => 'F',
Self::UppercaseG => 'G',
Self::UppercaseH => 'H',
Self::UppercaseI => 'I',
Self::UppercaseJ => 'J',
Self::UppercaseK => 'K',
Self::UppercaseL => 'L',
Self::UppercaseM => 'M',
Self::UppercaseN => 'N',
Self::UppercaseO => 'O',
Self::UppercaseP => 'P',
Self::UppercaseQ => 'Q',
Self::UppercaseR => 'R',
Self::UppercaseS => 'S',
Self::UppercaseT => 'T',
Self::UppercaseU => 'U',
Self::UppercaseV => 'V',
Self::UppercaseW => 'W',
Self::UppercaseX => 'X',
Self::UppercaseY => 'Y',
Self::UppercaseZ => 'Z',
Self::LowercaseA => 'a',
Self::LowercaseB => 'b',
Self::LowercaseC => 'c',
Self::LowercaseD => 'd',
Self::LowercaseE => 'e',
Self::LowercaseF => 'f',
Self::LowercaseG => 'g',
Self::LowercaseH => 'h',
Self::LowercaseI => 'i',
Self::LowercaseJ => 'j',
Self::LowercaseK => 'k',
Self::LowercaseL => 'l',
Self::LowercaseM => 'm',
Self::LowercaseN => 'n',
Self::LowercaseO => 'o',
Self::LowercaseP => 'p',
Self::LowercaseQ => 'q',
Self::LowercaseR => 'r',
Self::LowercaseS => 's',
Self::LowercaseT => 't',
Self::LowercaseU => 'u',
Self::LowercaseV => 'v',
Self::LowercaseW => 'w',
Self::LowercaseX => 'x',
Self::LowercaseY => 'y',
Self::LowercaseZ => 'z',
Self::Dquote => '"',
Self::Slash => '/',
Self::Arobase => '@',
Self::Caret => '^',
Self::Pipe => '|',
Self::Percent => '%',
Self::Dot => '.',
Self::Hash => '#',
Self::Underscore => '_',
Self::Colon => ':',
}
}
pub fn kak_escaped(&self) -> &'static str {
match self {
Self::Numeric0 => "0",
Self::Numeric1 => "1",
Self::Numeric2 => "2",
Self::Numeric3 => "3",
Self::Numeric4 => "4",
Self::Numeric5 => "5",
Self::Numeric6 => "6",
Self::Numeric7 => "7",
Self::Numeric8 => "8",
Self::Numeric9 => "9",
Self::UppercaseA => "A",
Self::UppercaseB => "B",
Self::UppercaseC => "C",
Self::UppercaseD => "D",
Self::UppercaseE => "E",
Self::UppercaseF => "F",
Self::UppercaseG => "G",
Self::UppercaseH => "H",
Self::UppercaseI => "I",
Self::UppercaseJ => "J",
Self::UppercaseK => "K",
Self::UppercaseL => "L",
Self::UppercaseM => "M",
Self::UppercaseN => "N",
Self::UppercaseO => "O",
Self::UppercaseP => "P",
Self::UppercaseQ => "Q",
Self::UppercaseR => "R",
Self::UppercaseS => "S",
Self::UppercaseT => "T",
Self::UppercaseU => "U",
Self::UppercaseV => "V",
Self::UppercaseW => "W",
Self::UppercaseX => "X",
Self::UppercaseY => "Y",
Self::UppercaseZ => "Z",
Self::LowercaseA => "a",
Self::LowercaseB => "b",
Self::LowercaseC => "c",
Self::LowercaseD => "d",
Self::LowercaseE => "e",
Self::LowercaseF => "f",
Self::LowercaseG => "g",
Self::LowercaseH => "h",
Self::LowercaseI => "i",
Self::LowercaseJ => "j",
Self::LowercaseK => "k",
Self::LowercaseL => "l",
Self::LowercaseM => "m",
Self::LowercaseN => "n",
Self::LowercaseO => "o",
Self::LowercaseP => "p",
Self::LowercaseQ => "q",
Self::LowercaseR => "r",
Self::LowercaseS => "s",
Self::LowercaseT => "t",
Self::LowercaseU => "u",
Self::LowercaseV => "v",
Self::LowercaseW => "w",
Self::LowercaseX => "x",
Self::LowercaseY => "y",
Self::LowercaseZ => "z",
Self::Dquote => "\\\"",
Self::Slash => "/",
Self::Arobase => "@",
Self::Caret => "^",
Self::Pipe => "|",
Self::Percent => "%",
Self::Dot => ".",
Self::Hash => "#",
Self::Underscore => "_",
Self::Colon => ":",
}
}
}
impl FromStr for Register {
type Err = KakError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"0" => Ok(Self::Numeric0),
"1" => Ok(Self::Numeric1),
"2" => Ok(Self::Numeric2),
"3" => Ok(Self::Numeric3),
"4" => Ok(Self::Numeric4),
"5" => Ok(Self::Numeric5),
"6" => Ok(Self::Numeric6),
"7" => Ok(Self::Numeric7),
"8" => Ok(Self::Numeric8),
"9" => Ok(Self::Numeric9),
"A" => Ok(Self::UppercaseA),
"B" => Ok(Self::UppercaseB),
"C" => Ok(Self::UppercaseC),
"D" => Ok(Self::UppercaseD),
"E" => Ok(Self::UppercaseE),
"F" => Ok(Self::UppercaseF),
"G" => Ok(Self::UppercaseG),
"H" => Ok(Self::UppercaseH),
"I" => Ok(Self::UppercaseI),
"J" => Ok(Self::UppercaseJ),
"K" => Ok(Self::UppercaseK),
"L" => Ok(Self::UppercaseL),
"M" => Ok(Self::UppercaseM),
"N" => Ok(Self::UppercaseN),
"O" => Ok(Self::UppercaseO),
"P" => Ok(Self::UppercaseP),
"Q" => Ok(Self::UppercaseQ),
"R" => Ok(Self::UppercaseR),
"S" => Ok(Self::UppercaseS),
"T" => Ok(Self::UppercaseT),
"U" => Ok(Self::UppercaseU),
"V" => Ok(Self::UppercaseV),
"W" => Ok(Self::UppercaseW),
"X" => Ok(Self::UppercaseX),
"Y" => Ok(Self::UppercaseY),
"Z" => Ok(Self::UppercaseZ),
"a" => Ok(Self::LowercaseA),
"b" => Ok(Self::LowercaseB),
"c" => Ok(Self::LowercaseC),
"d" => Ok(Self::LowercaseD),
"e" => Ok(Self::LowercaseE),
"f" => Ok(Self::LowercaseF),
"g" => Ok(Self::LowercaseG),
"h" => Ok(Self::LowercaseH),
"i" => Ok(Self::LowercaseI),
"j" => Ok(Self::LowercaseJ),
"k" => Ok(Self::LowercaseK),
"l" => Ok(Self::LowercaseL),
"m" => Ok(Self::LowercaseM),
"n" => Ok(Self::LowercaseN),
"o" => Ok(Self::LowercaseO),
"p" => Ok(Self::LowercaseP),
"q" => Ok(Self::LowercaseQ),
"r" => Ok(Self::LowercaseR),
"s" => Ok(Self::LowercaseS),
"t" => Ok(Self::LowercaseT),
"u" => Ok(Self::LowercaseU),
"v" => Ok(Self::LowercaseV),
"w" => Ok(Self::LowercaseW),
"x" => Ok(Self::LowercaseX),
"y" => Ok(Self::LowercaseY),
"z" => Ok(Self::LowercaseZ),
"\"" | "dquote" => Ok(Self::Dquote),
"/" | "slash" => Ok(Self::Slash),
"@" | "arobase" => Ok(Self::Arobase),
"^" | "caret" => Ok(Self::Caret),
"|" | "pipe" => Ok(Self::Pipe),
"%" | "percent" => Ok(Self::Percent),
"." | "dot" => Ok(Self::Dot),
"#" | "hash" => Ok(Self::Hash),
"_" | "underscore" => Ok(Self::Underscore),
":" | "colon" => Ok(Self::Colon),
_ => Err(KakError::Parse(format!(
"Register '{s}' could not be parsed"
))),
}
}
}
#[cfg(test)]
mod test {
use super::*;

View File

@ -5,15 +5,19 @@
// Cannot be fixed
#![allow(clippy::multiple_crate_versions)]
#![allow(clippy::struct_excessive_bools)]
// TODO: Remove
#![allow(dead_code, unused_imports)]
mod errors;
mod math_eval;
mod set;
mod shuf;
mod sort;
mod stdin;
mod trim;
mod uniq;
mod xargs;
mod utils;
// mod xargs;
use clap::{Parser, Subcommand};
use kakplugin::{display_message, get_var, KakError};
@ -31,13 +35,21 @@ struct Cli {
#[derive(Subcommand, Debug)]
enum Commands {
#[clap(about = "Sorts selections based on content or content regex match")]
Sort(sort::Options),
#[clap(about = "Shuffle selections")]
Shuf(shuf::Options),
#[clap(about = "Find unique selections based on optional regex match")]
Uniq(uniq::Options),
#[clap(visible_aliases = &["bc", "eval"])]
#[clap(about = "Evaluate selections as a math expression", visible_aliases = &["bc", "eval"])]
MathEval(math_eval::Options),
#[clap(about = "Trim every selection")]
Trim(trim::Options),
Xargs(xargs::Options),
#[clap(about = "Perform set operations on selections")]
Set(set::Options),
// #[clap(about = "")]
// Xargs(xargs::Options),
#[clap(about = "Pass each selection null terminated to a command")]
Stdin(stdin::Options),
}
@ -63,8 +75,7 @@ fn main() {
}
fn run() -> Result<String, KakError> {
let options =
Cli::try_parse().map_err(|e| KakError::Parse(format!("Argument parse error: {e}")))?;
let options = Cli::try_parse().map_err(|e| KakError::Custom(format!("{e}")))?;
match &options.command {
Commands::Sort(o) => sort::sort(o),
@ -72,7 +83,8 @@ fn run() -> Result<String, KakError> {
Commands::Uniq(o) => uniq::uniq(o),
Commands::MathEval(o) => math_eval::math_eval(o),
Commands::Trim(o) => trim::trim(o),
Commands::Xargs(o) => xargs::xargs(o),
Commands::Set(o) => set::set(o),
// Commands::Xargs(o) => xargs::xargs(o),
Commands::Stdin(o) => stdin::stdin(o),
}
}

314
src/set.rs Normal file
View File

@ -0,0 +1,314 @@
// use crate::utils;
use clap::ArgEnum;
use kakplugin::{
get_selections, get_selections_with_desc, set_selections, set_selections_desc, types::Register,
KakError, Selection, SelectionWithDesc,
};
use linked_hash_map::LinkedHashMap;
use linked_hash_set::LinkedHashSet;
use regex::Regex;
use std::{collections::HashSet, io::Write, str::FromStr};
#[derive(clap::StructOpt, Debug)]
pub struct Options {
#[clap(min_values = 1, max_values = 3)]
args: Vec<String>,
#[clap(short = 'T')]
no_trim: bool,
// #[clap(short, long)]
// regex: Option<Regex>,
// #[clap(short, long)]
// ignore_case: bool,
// #[clap(short = 'S', long)]
// no_skip_whitespace: bool,
}
#[derive(Clone, Debug)]
enum Operation {
Intersect,
Subtract,
Union,
Compare,
}
impl Operation {
pub fn to_char(&self) -> char {
match self {
Self::Intersect => '&',
Self::Subtract => '-',
Self::Union => '+',
Self::Compare => '?',
}
}
}
impl FromStr for Operation {
type Err = KakError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"intersect" | "and" | "&" => Ok(Self::Intersect),
"subtract" | "not" | "minus" | "-" | "\\" => Ok(Self::Subtract),
"union" | "or" | "plus" | "+" => Ok(Self::Union),
"compare" | "cmp" | "?" | "=" => Ok(Self::Compare),
_ => Err(KakError::Parse(format!(
"Set operation '{s}' could not be parsed"
))),
}
}
}
pub fn set(options: &Options) -> Result<String, KakError> {
let (left_register, operation, right_register) = parse_arguments(&options.args[..])?;
// Underscore is a special case. We will treat it as the current selection
let (left_selections, right_selections) = match (&left_register, &right_register) {
(Register::Underscore, r) => {
let l_selections = get_selections()?;
kakplugin::restore_register(r)?;
let r_selections = get_selections()?;
(l_selections, r_selections)
}
(l, Register::Underscore) => {
let r_selections = get_selections()?;
kakplugin::restore_register(l)?;
let l_selections = get_selections()?;
(l_selections, r_selections)
}
(l, r) => {
kakplugin::restore_register(l)?;
let l_selections = get_selections()?;
kakplugin::restore_register(r)?;
let r_selections = get_selections()?;
(l_selections, r_selections)
}
};
let (left_ordered_counts, right_ordered_counts) = (
to_ordered_counts(options, left_selections),
to_ordered_counts(options, right_selections),
);
let (left_keys, right_keys) = (
left_ordered_counts
.keys()
.collect::<LinkedHashSet<&Selection>>(),
right_ordered_counts
.keys()
.collect::<LinkedHashSet<&Selection>>(),
);
let result = key_set_operation(&operation, &left_keys, &right_keys);
match &operation {
Operation::Compare => compare(
&left_register,
&right_register,
&result,
&left_ordered_counts,
&right_ordered_counts,
)?,
Operation::Intersect | Operation::Subtract | Operation::Union => print_result(&result)?,
}
Ok(match &operation {
Operation::Compare => format!("Compared {} selections", result.len()),
op => format!(
"{}{}{} returned {} selections",
left_register.to_char(),
op.to_char(),
right_register.to_char(),
result.len()
),
})
}
fn print_result(key_set_operation_result: &LinkedHashSet<&Selection>) -> Result<(), KakError> {
// Manually set selections so we don't have to allocate a string
let mut f = kakplugin::open_command_fifo()?;
// Send all of this into an evaluate-commands block
// -save-regs '"'
write!(
f,
r#"evaluate-commands %{{
set-register '"'"#
)?;
for k in key_set_operation_result {
write!(f, " '{}\n'", kakplugin::escape(k))?;
}
write!(
f,
r#";
edit -scratch '*kakplugin-set*';
execute-keys '%<a-R>_';
}}"#
)?;
f.flush()?;
Ok(())
}
fn compare(
left_register: &Register,
right_register: &Register,
key_set_operation_result: &LinkedHashSet<&Selection>,
left_ordered_counts: &LinkedHashMap<Selection, usize>,
right_ordered_counts: &LinkedHashMap<Selection, usize>,
) -> Result<(), KakError> {
// Manually set selections so we don't have to allocate a string
let mut f = kakplugin::open_command_fifo()?;
// Send all of this into an evaluate-commands block
write!(
f,
// -save-regs '"'
r#"evaluate-commands -save-regs '"' %{{
set-register '"'"#
)?;
write!(
f,
" '?\t{}\t{}\tselection\n'",
left_register.to_char(),
right_register.to_char()
)?;
for k in key_set_operation_result {
let left_count = left_ordered_counts.get(&k as &str).unwrap_or(&0);
let right_count = right_ordered_counts.get(&k as &str).unwrap_or(&0);
write!(
f,
" '{}\t{}\t{}\t{}\n'",
match (*left_count == 0, *right_count == 0) {
(true, true) => "?",
(true, false) => ">",
(false, true) => "<",
(false, false) => "=",
},
left_count,
right_count,
// TODO: Do we want to escape the \n to \\n?
// kakplugin::escape(k.replace('\n', "\\n")),
kakplugin::escape(k),
)?;
}
write!(
f,
r#";
edit -scratch '*kakplugin-set*';
execute-keys '%<a-R><a-;>3<a-W>L)<a-space>_vb';
}}"#
)?;
f.flush()?;
Ok(())
}
fn to_ordered_counts(options: &Options, sels: Vec<Selection>) -> LinkedHashMap<Selection, usize> {
let mut ret = LinkedHashMap::new();
for i in sels.into_iter() {
let key = if options.no_trim {
i
} else {
i.trim().to_string()
};
if key.is_empty() {
// We don't want to even pretend to look at empty keys
continue;
}
let entry: &mut usize = ret.entry(key).or_insert(0);
*entry = entry.saturating_add(1);
}
ret
}
fn key_set_operation<'a>(
operation: &Operation,
left_keys: &LinkedHashSet<&'a Selection>,
right_keys: &LinkedHashSet<&'a Selection>,
) -> LinkedHashSet<&'a Selection> {
match operation {
Operation::Intersect => left_keys
.intersection(&right_keys)
// .into_iter()
// TODO: Remove this
.cloned()
.collect(),
Operation::Subtract => left_keys
.difference(&right_keys)
.into_iter()
// TODO: Remove this
.cloned()
.collect(),
Operation::Compare | Operation::Union => left_keys
.union(&right_keys)
.into_iter()
// TODO: Remove this
.cloned()
.collect(),
// TODO: Symmetric difference?
}
}
fn parse_arguments(args: &[String]) -> Result<(Register, Operation, Register), KakError> {
let args = if args.len() == 1 {
// They gave us something like "a-b" or "c?d"
args.iter()
.flat_map(|s: &String| s.trim().chars())
.map(|c| String::from(c))
.collect::<Vec<String>>()
} else {
// They gave us something like "a - b" or "c compare d"
args.iter().cloned().collect()
};
let (left_register, middle, right_register) = match &args[..] {
[l, r] => {
// They only gave us two arguments like "- a" or "b -"
match (Operation::from_str(l), Operation::from_str(r)) {
// If the operation is on the left, then the _ register is the leftmost one
(Ok(o), Err(_)) => Ok((Register::Underscore, o, Register::from_str(r)?)),
// If the operation is on the right, then the _ register is the rightmost one
(Err(_), Ok(o)) => Ok((Register::from_str(l)?, o, Register::Underscore)),
(Ok(_), Ok(_)) => Err(KakError::Custom(format!(
"Arguments '{l}' and '{r}' cannot both be operations"
))),
(Err(_), Err(_)) => Err(KakError::Custom(format!(
"One argument must be an operation"
))),
}
}
[l, middle, r] => {
// They gave us three arguments like "a - b" or "_ + a"
Ok((
Register::from_str(l)?,
Operation::from_str(middle)?,
Register::from_str(r)?,
))
}
_ => Err(KakError::Custom(format!(
"Invalid arguments to set command"
))),
}?;
if left_register == right_register {
return Err(KakError::Custom(format!(
"Registers passed are the same: '{}'",
left_register.to_char()
)));
}
Ok((left_register, middle, right_register))
}

View File

@ -125,7 +125,7 @@ pub fn sort(options: &Options) -> Result<String, KakError> {
.subselections_register
.map::<Result<_, KakError>, _>(|c| {
let subselections = get_selections_with_desc()?;
kakplugin::exec(&format!("exec {}", c))?;
kakplugin::cmd(&format!("exec {}", c))?;
Ok(subselections)
})
.transpose()?;

View File

@ -34,7 +34,6 @@ pub fn stdin(options: &Options) -> Result<String, KakError> {
)?;
// Wait for the background process to exit
// TODO: Do not use a string
handle
.join()
.map_err(|_e| KakError::Custom("Could not join background process".to_string()))??;

View File

@ -1,12 +1,11 @@
use crate::utils;
use kakplugin::{
get_selections_desc, get_selections_with_desc, set_selections, set_selections_desc, KakError,
SelectionWithDesc,
};
use regex::Regex;
use std::{
collections::{hash_map::DefaultHasher, BTreeSet},
hash::{Hash, Hasher},
};
use std::collections::BTreeSet;
#[derive(clap::StructOpt, Debug)]
pub struct Options {
#[clap(index = 1)]
@ -26,40 +25,18 @@ pub fn uniq(options: &Options) -> Result<String, KakError> {
let new_selections: Vec<Option<SelectionWithDesc>> = selections
.into_iter()
// Create a BTreeSet of hashes of selections. This way, string content is not stored, but uniqueness 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 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
.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);
.scan(BTreeSet::new(), |state, sd| {
let hash = utils::get_hash(
&sd.content,
!options.no_skip_whitespace,
options.regex.as_ref(),
options.ignore_case,
);
// Try inserting to the hash
if state.insert(hasher.finish()) {
if state.insert(hash) {
// True if this is a string we haven't seen before
Some(Some(s))
Some(Some(sd))
} else {
// Nothing was inserted because we already saw this string
// Return Some(None) so the iterator can continue

54
src/utils.rs Normal file
View File

@ -0,0 +1,54 @@
use kakplugin::Selection;
use regex::Regex;
use std::{
collections::hash_map::DefaultHasher,
hash::{Hash, Hasher},
};
pub(crate) fn get_key(
selection: &Selection,
skip_whitespace: bool,
regex: Option<&Regex>,
ignore_case: bool,
) -> String {
// Strip whitespace if requested
let mut key = if skip_whitespace {
selection.as_str()
} else {
selection.trim()
};
// If they requested a regex match, set the key to the string slice of that match
if let Some(regex_match) = (|| {
let captures = 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
// Lowercase at the end to not mangle regex
if ignore_case {
key.to_lowercase()
} else {
// TODO: Do not perform an allocation here
key.to_string()
}
}
/// Get a key out of a selection based on options
pub(crate) fn get_hash(
selection: &Selection,
skip_whitespace: bool,
regex: Option<&Regex>,
ignore_case: bool,
) -> u64 {
let mut hasher = DefaultHasher::new();
get_key(selection, skip_whitespace, regex, ignore_case).hash(&mut hasher);
hasher.finish()
}