Add set command
This commit is contained in:
parent
ec3c685a16
commit
01a7063c22
17
Cargo.lock
generated
17
Cargo.lock
generated
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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")?
|
||||
))?;
|
||||
|
@ -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::*;
|
||||
|
24
src/main.rs
24
src/main.rs
@ -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
314
src/set.rs
Normal 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))
|
||||
}
|
@ -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()?;
|
||||
|
@ -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()))??;
|
||||
|
47
src/uniq.rs
47
src/uniq.rs
@ -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
54
src/utils.rs
Normal 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()
|
||||
}
|
Loading…
Reference in New Issue
Block a user