rust-selection-sort.kak/src/invert.rs

116 lines
4.8 KiB
Rust
Raw Normal View History

2022-08-10 20:08:04 -04:00
use kakplugin::{
2022-10-02 13:12:58 -04:00
get_selections_desc, set_selections_desc, types::MaybeSplit, KakError, SelectionDesc,
2022-08-10 20:08:04 -04:00
};
2023-05-17 21:36:14 -04:00
#[derive(clap::Args, Debug)]
pub struct Options {
#[clap(short, long, help = "Do not include newlines")]
no_newline: bool,
2022-09-03 02:02:12 -04:00
#[clap(short, long, help = "Invert by line instead of by entire document")]
line: bool,
}
pub fn invert(options: &Options) -> Result<String, KakError> {
2022-08-10 20:08:04 -04:00
// The selections to invert
let mut split_selections_desc: Vec<(usize, Vec<SelectionDesc>)> = {
// Split by multiline so subtraction is defined (see below)
// Group by row, so for a given document row, subtraction can iterate over the Vec
2022-08-14 20:44:44 -04:00
get_selections_desc(Some("<a-s>"))?
2024-05-29 16:18:08 -04:00
.chunk_by(|a, b| a.left.row == b.left.row)
2022-08-10 20:08:04 -04:00
.map(|sds| (sds[0].left.row, sds.to_vec()))
.collect()
};
2022-08-14 20:44:44 -04:00
let count_selections = split_selections_desc.len();
2022-09-03 02:02:12 -04:00
let whole_document_selection_command = match (options.line, options.no_newline) {
// Select everything and only keep non-newlines
2022-09-03 02:02:12 -04:00
(false, true) => "%s^[^\\n]+<ret>",
// Select everything and split
2022-09-03 02:02:12 -04:00
(false, false) => "%<a-s>",
// Select entire line, then remove newline
(true, true) => "x<a-s>s^[^\\n]+<ret>",
2022-09-03 02:02:12 -04:00
// Select entire line, including newline
(true, false) => "x<a-s>",
};
2022-08-10 20:08:04 -04:00
let document_descs: Vec<SelectionDesc> = {
// 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.<?>)
get_selections_desc(Some(whole_document_selection_command))?
2022-08-14 20:44:44 -04:00
.into_iter()
2022-08-11 17:37:08 -04:00
// dd - The full row selectiondesc, spanning from col 1 to the rightmost col, for every row in the file
2022-10-02 09:57:37 -04:00
.flat_map(|dd: SelectionDesc| {
2022-08-10 20:08:04 -04:00
// 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]
}
}
})
.collect()
};
set_selections_desc(document_descs.iter())?;
kakplugin::cmd("exec '<a-_>'")?;
2022-08-14 20:44:44 -04:00
Ok(format!("Inverted {} selections", count_selections))
2022-08-10 20:08:04 -04:00
}
2022-10-02 09:57:37 -04:00
/// Subtract an iterator of `SelectionDesc`s from a given `SelectionDesc`
2022-08-10 20:08:04 -04:00
///
/// This returns a `Vec` because splitting in the middle can create two `SelectionDesc`s
///
2022-10-02 09:57:37 -04:00
/// * `selection_desc` - The primary `SelectionDesc` to be subtracted from
2022-08-10 20:08:04 -04:00
/// * `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<SD1, SD2>(
selection_desc: SD1,
mut selections_desc_to_subtract: Vec<SD2>,
) -> Vec<SelectionDesc>
where
SD1: AsRef<SelectionDesc>,
SD2: AsRef<SelectionDesc> + 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();
}
2022-10-02 09:57:37 -04:00
MaybeSplit::JustTwo(selectiondesc_a, selectiondesc_b) => {
2022-08-10 20:08:04 -04:00
// 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
2022-10-02 09:57:37 -04:00
ret.push(selectiondesc_a);
rightmost_selection_desc = selectiondesc_b;
2022-08-10 20:08:04 -04:00
}
}
}
// 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
}