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

216 lines
7.6 KiB
Rust
Raw Normal View History

2022-02-21 17:23:34 -05:00
use alphanumeric_sort::compare_str;
2022-06-05 00:59:05 -04:00
use kakplugin::{self, get_selections_with_desc, open_command_fifo, KakError, SelectionWithDesc};
2022-02-21 17:23:34 -05:00
use regex::Regex;
2022-03-09 21:25:50 -05:00
use std::{cmp::Ordering, io::Write};
2022-02-21 17:23:34 -05:00
#[derive(clap::StructOpt, Debug)]
pub struct Options {
2022-02-21 17:23:34 -05:00
#[clap(index = 1)]
regex: Option<Regex>,
#[clap(short = 's', long)]
subselections_register: Option<char>,
// TODO: Can we invert a boolean? This name is terrible
2022-02-27 18:14:42 -05:00
#[clap(short = 'S', long, parse(try_from_str = invert_bool), default_value_t)]
2022-02-21 17:23:34 -05:00
no_skip_whitespace: bool,
#[clap(short, long)]
lexicographic_sort: bool,
#[clap(short, long)]
reverse: bool,
#[clap(short, long)]
ignore_case: bool,
2022-05-09 22:23:59 -04:00
// TODO: Sort by character ([xba] => [abx])
2022-02-21 17:23:34 -05:00
}
2022-02-27 18:14:42 -05:00
fn invert_bool(s: &str) -> Result<bool, &'static str> {
// Invert the boolean
match s {
"false" => Ok(true),
"true" => Ok(false),
_ => Err("Unparsable boolean value"),
}
}
2022-02-21 17:23:34 -05:00
struct SortableSelection<'a> {
/// The content of the selection
2022-03-09 21:25:50 -05:00
selection: &'a SelectionWithDesc,
2022-02-21 17:23:34 -05:00
/// The string used to compare the content
content_comparison: &'a str,
/// Any subselections
subselections: Vec<&'a str>,
}
2022-02-21 18:27:46 -05:00
/// Gets a Vec of sortable selections with a given list of subselections and descriptions
2022-03-09 21:25:50 -05:00
/// TODO: Implement sort by subselection
// fn get_sortable_selections_subselections<'a, 'b, 'tmp, S: AsRef<str> + std::fmt::Debug + 'a>(
// options: &'b Options,
// selections: Vec<SelectionWithDesc>,
// subselections: Vec<SelectionWithDesc>,
// ) -> Result<Vec<SortableSelection<'a>>, KakMessage> {
// let mut sortable_selections = selections
// .iter()
// .zip(selections_desc.iter())
// .map(|(s, sd)| {
// Ok((
// to_sortable_selection(s.as_ref(), options),
// SelectionDesc::from_str(sd.as_ref())?,
// ))
// })
// .collect::<Result<Vec<(SortableSelection, SelectionDesc)>, KakMessage>>()?;
// let mut subselections = subselections
// .iter()
// .zip(subselections_desc.iter())
// // Bind selection with associated description
// // Sort descriptions so left is always <= right
// .map(|(s, sd)| Ok((s.as_ref(), SelectionDesc::from_str(sd.as_ref())?.sort())))
// .collect::<Result<Vec<(&str, SelectionDesc)>, KakMessage>>()?;
// // Sort subselections by description
// subselections.sort_by(|(_, ssd_a), (_, ssd_b)| ssd_a.cmp(ssd_b));
// // For each selection, check if they contain any subselections
// // If so, add them to the subselections vector
// // TODO: This is O(n^2), but can be made more efficient since subselections is sorted
// for (s, s_desc) in &mut sortable_selections {
// for i in &subselections {
// if s_desc.contains(&i.1) {
// s.subselections.push(i.0);
// }
// }
// }
// sortable_selections.sort_by(|(a, _), (b, _)| {
// // First, check if there are any subselection comparisons to be made
// // If one has more subselections than the other, stop comparing
// for (a_subsel, b_subsel) in a.subselections.iter().zip(b.subselections.iter()) {
// match a_subsel.cmp(b_subsel) {
// // These subselections are equal, so we can't do anything
// Ordering::Equal => continue,
// // We found a difference, so return the comparison
// o => return o,
// }
// }
// // No subselections mismatched, so compare the (possibly trimmed) content
// a.content_comparison.cmp(b.content_comparison)
// });
// Ok(sortable_selections.into_iter().map(|(s, _)| s).collect())
// }
2022-02-21 17:23:34 -05:00
fn to_sortable_selection<'a, 'b>(
2022-03-09 21:25:50 -05:00
selection: &'a SelectionWithDesc,
options: &'b Options,
2022-02-21 17:23:34 -05:00
) -> SortableSelection<'a> {
if options.no_skip_whitespace {
2022-02-21 17:23:34 -05:00
SortableSelection {
2022-06-09 18:11:03 -04:00
selection,
2022-03-09 21:25:50 -05:00
content_comparison: selection.content.as_str(),
2022-02-21 17:23:34 -05:00
subselections: vec![],
}
} else {
SortableSelection {
2022-06-09 18:11:03 -04:00
selection,
2022-03-09 21:25:50 -05:00
content_comparison: selection.content.as_str().trim(),
2022-02-21 17:23:34 -05:00
subselections: vec![],
}
}
}
2022-06-05 00:59:05 -04:00
pub fn sort(options: &Options) -> Result<String, KakError> {
2022-03-09 21:25:50 -05:00
// subselections is Some if the user requests it in subselections_register
// It will "exec z" to restore the selections before setting selections
// If subselections is None, "exec z" is not called
let subselections: Option<Vec<SelectionWithDesc>> = options
2022-02-21 17:23:34 -05:00
.subselections_register
2022-06-05 00:59:05 -04:00
.map::<Result<_, KakError>, _>(|c| {
2022-03-09 21:25:50 -05:00
let subselections = get_selections_with_desc()?;
2022-06-06 18:01:49 -04:00
kakplugin::cmd(&format!("exec {}", c))?;
2022-03-09 21:25:50 -05:00
Ok(subselections)
2022-02-21 17:23:34 -05:00
})
.transpose()?;
let selections = get_selections_with_desc()?;
2022-03-09 21:25:50 -05:00
let mut zipped: Vec<SortableSelection<'_>> = match (&options.regex, &subselections) {
(Some(_), Some(_)) => {
2022-06-05 00:59:05 -04:00
return Err(KakError::Custom(
"Cannot pass regex and subselections register".to_string(),
))
2022-03-09 21:25:50 -05:00
}
(None, None) => {
// Do a regular sort on the content
selections
.iter()
.map(|s| to_sortable_selection(s, options))
.collect()
}
(Some(regex), None) => {
// Sort based on the regular expression
selections
.iter()
.map(|s| {
let mut sortable_selection = to_sortable_selection(s, options);
if let Some(regex_match) = (|| {
let captures = regex.captures(sortable_selection.content_comparison)?;
captures
.get(1)
.or_else(|| captures.get(0))
.map(|m| m.as_str())
})() {
sortable_selection.content_comparison = regex_match;
}
sortable_selection
})
.collect()
}
(None, _) => {
// Sort based on subselections
2022-06-05 00:59:05 -04:00
return Err(KakError::NotImplemented(
"Sort by subselection is not yet implemented",
2022-03-09 21:25:50 -05:00
));
2022-02-21 17:23:34 -05:00
}
};
zipped.sort_by(|a, b| {
2022-03-09 21:25:50 -05:00
// First, try sorting by subselection. This won't iterate anything if either is None (regex and default mode)
2022-02-21 17:23:34 -05:00
for (a_subselection, b_subselection) in a.subselections.iter().zip(b.subselections.iter()) {
let comparison = if options.lexicographic_sort {
2022-02-21 17:23:34 -05:00
a_subselection.cmp(b_subselection)
} else {
compare_str(a_subselection, b_subselection)
};
match comparison {
Ordering::Less | Ordering::Greater => return comparison,
Ordering::Equal => {}
}
}
2022-03-09 21:25:50 -05:00
// Otherwise, default to comparing the content
if options.lexicographic_sort {
2022-02-21 17:23:34 -05:00
a.content_comparison.cmp(b.content_comparison)
} else {
compare_str(a.content_comparison, b.content_comparison)
}
});
let mut f = open_command_fifo()?;
write!(f, "reg '\"'")?;
let iter: Box<dyn Iterator<Item = _>> = if options.reverse {
2022-02-21 17:23:34 -05:00
Box::new(zipped.iter().rev())
} else {
Box::new(zipped.iter())
};
for i in iter {
2022-03-09 21:25:50 -05:00
let new_selection = i.selection.content.replace('\'', "''");
2022-02-21 17:23:34 -05:00
write!(f, " '{}'", new_selection)?;
}
write!(f, " ; exec R;")?;
2022-03-07 21:54:10 -05:00
f.flush()?;
2022-02-21 17:23:34 -05:00
2022-06-05 00:59:05 -04:00
Ok(format!("Sorted {} selections", zipped.len()))
2022-02-21 17:23:34 -05:00
}