diff --git a/src/invert.rs b/src/invert.rs new file mode 100644 index 0000000..8c950ed --- /dev/null +++ b/src/invert.rs @@ -0,0 +1,100 @@ +use kakplugin::{ + get_selections_desc, set_selections_desc, types::MaybeSplit, AnchorPosition, KakError, + SelectionDesc, +}; +use std::{fs, str::FromStr}; +#[derive(clap::StructOpt, Debug)] +pub struct Options; +pub fn invert(_options: &Options) -> Result { + // The selections to invert + let mut split_selections_desc: Vec<(usize, Vec)> = { + // Split by multiline so subtraction is defined (see below) + let mut ret: Vec = get_selections_desc(Some(""))?.into_iter().collect(); + ret.sort(); + // Group by row, so for a given document row, subtraction can iterate over the Vec + ret.group_by(|a, b| a.left.row == b.left.row) + .map(|sds| (sds[0].left.row, sds.to_vec())) + .collect() + }; + + let document_descs: Vec = { + // Every line in the document as a selectiondesc + // Split by line because subtracting cross-multiline is not always defined for multiline selection descs (ex: 1.1,3.3 - 2.1,3.3 = 1.1,1.) + let mut ret = get_selections_desc(Some("%"))?; + ret.sort(); + ret.into_iter() + // dd - The full row selectiondesc, spanning from col 1 to the rightmost col + .map(|dd: SelectionDesc| { + // For every line, if there are selections to subtract, subtract them all + match split_selections_desc + .binary_search_by(|sd_search| sd_search.0.cmp(&dd.left.row)) + { + Ok(idx) => { + // There is at least one SelectionDesc that needs to be subtracted from dd + subtract_all_selections_desc(dd, split_selections_desc.remove(idx).1) + } + Err(_) => { + // There are no SelectionDesc entries that need to be subtracted from this row. return it + vec![dd] + } + } + }) + .flatten() + .collect() + }; + + set_selections_desc(document_descs.iter())?; + + kakplugin::cmd("exec ''")?; + + Ok(format!("Invert {} selections", 0)) +} + +/// Subtract an iterator of `SelectionDesc`s from a given SelectionDesc +/// +/// This returns a `Vec` because splitting in the middle can create two `SelectionDesc`s +/// +/// * `selection_desc` - The primary SelectionDesc to be subtracted from +/// * `selections_desc_to_subtract` - `Vec` of `SelectionDesc`s from `sd`. Must be an owned `Vec` because it needs to be sorted +fn subtract_all_selections_desc( + selection_desc: SD1, + mut selections_desc_to_subtract: Vec, +) -> Vec +where + SD1: AsRef, + SD2: AsRef + Ord, +{ + // If it is sorted, the selections to subtract will be in left to right order + // This way, we can store just the rightmost `selection_desc` + selections_desc_to_subtract.sort(); + + let mut rightmost_selection_desc: SelectionDesc = selection_desc.as_ref().clone(); + let mut ret = vec![]; + + for sd in selections_desc_to_subtract { + match rightmost_selection_desc.as_ref().subtract(sd.as_ref()) { + MaybeSplit::Nothing => { + // Subtraction yeilded no selections. This selection desc needs to be excluded + return ret; + } + MaybeSplit::Just(sd) => { + // There was a successful split, but it was a prefix/suffix truncation + // We don't know if more selections will cut this selection, so continue + // TODO: Replace Just with JustLeft and JustRight? + rightmost_selection_desc = sd.as_ref().clone(); + } + MaybeSplit::JustTwo(sda, sdb) => { + // There was a split in the middle of the selection + // Put the left half into the return vector and keep checking if the right half needs more work + ret.push(sda); + rightmost_selection_desc = sdb; + } + } + } + + // If we got here, the iterator ran out of things to subtract from us + // Push whatever is in the rightmost selection desc and continue + ret.push(rightmost_selection_desc); + + ret +} diff --git a/src/main.rs b/src/main.rs index 733f1eb..165447d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -48,6 +48,8 @@ enum Commands { Shuf(shuf::Options), #[clap(about = "Find unique selections based on optional regex match")] Uniq(uniq::Options), + #[clap(about = "Invert all selections")] + Invert(invert::Options), #[clap(about = "Evaluate selections as a math expression", visible_aliases = &["bc", "eval"])] MathEval(math_eval::Options), #[clap(about = "Trim every selection")] @@ -100,6 +102,7 @@ fn run() -> Result { Commands::Sort(o) => sort::sort(o), Commands::Shuf(o) => shuf::shuf(o), Commands::Uniq(o) => uniq::uniq(o), + Commands::Invert(o) => invert::invert(o), Commands::MathEval(o) => math_eval::math_eval(o), Commands::Trim(o) => trim::trim(o), Commands::Set(o) => set::set(o),