Fix clippy errors
This commit is contained in:
parent
b875e09927
commit
c9974d4d29
@ -644,7 +644,7 @@ After `shuf`:
|
||||
Sort selections by regular expression or content
|
||||
|
||||
* `-S`/`--no-skip-whitespace` - Do not treat trimmed value of selections when sorting (by default, surrounding selection whitespace is trimmed before comparison)
|
||||
* `-l`/`--lexicographic-sort` - Sort numbers lexicographically (`10 > 2`)
|
||||
* `-L`/`--no-lexicographic-sort` - Do not sort numbers lexicographically (`10 < 2` when `-L` is passed)
|
||||
* `-r`/`--reverse` - Reverse sorting
|
||||
* `-i`/`--ignore-case` - Ignore case when sorting
|
||||
* `[REGEX]` - Optional regex comparison key
|
||||
|
@ -3,7 +3,9 @@ pub mod types;
|
||||
pub use errors::KakError;
|
||||
pub use shell_words::ParseError;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
env,
|
||||
fmt::Display,
|
||||
fs::{self, File, OpenOptions},
|
||||
io::{BufWriter, Write},
|
||||
str::FromStr,
|
||||
@ -40,7 +42,7 @@ where
|
||||
///
|
||||
/// Will return `Err` if command fifo could not be opened, read from, or written to
|
||||
// TODO: Use AsRef
|
||||
pub fn get_selections_desc_unsorted<S>(keys: Option<S>) -> Result<Vec<SelectionDesc>, KakError>
|
||||
pub fn get_selections_desc_unordered<S>(keys: Option<S>) -> Result<Vec<SelectionDesc>, KakError>
|
||||
where
|
||||
S: AsRef<str>,
|
||||
{
|
||||
@ -63,13 +65,17 @@ where
|
||||
// }
|
||||
// }
|
||||
|
||||
/// Return a vec of SelectionWithDesc. The returned vec is in order according to SelectionDesc
|
||||
///
|
||||
/// For example, if your primary selection is selection 2 of 3, the returned vec's order will be selection 2, 3, then 1
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Will return `Err` if command fifo could not be opened, read from, or written to,
|
||||
/// or if `selections.len() != selections_desc.len`
|
||||
pub fn get_selections_with_desc(keys: Option<&'_ str>) -> Result<Vec<SelectionWithDesc>, KakError> {
|
||||
let mut selections = get_selections(keys)?;
|
||||
let selections_desc = get_selections_desc_unsorted(keys)?;
|
||||
let selections_desc = get_selections_desc_unordered(keys)?;
|
||||
|
||||
if selections.len() != selections_desc.len() {
|
||||
return Err(KakError::KakResponse(format!(
|
||||
@ -109,13 +115,24 @@ pub fn get_selections_with_desc(keys: Option<&'_ str>) -> Result<Vec<SelectionWi
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
}
|
||||
|
||||
/// Return a vec of SelectionWithDesc, sorted in file (SelectionDesc) order
|
||||
///
|
||||
/// For example, the returned vec's order will be selection 1, 2, then 3 regardless of the primary selection
|
||||
pub fn get_selections_with_desc_ordered(
|
||||
keys: Option<&'_ str>,
|
||||
) -> Result<Vec<SelectionWithDesc>, KakError> {
|
||||
let mut ret = get_selections_with_desc(keys)?;
|
||||
ret.sort_by_key(|s| s.desc.sort());
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
/// # Errors
|
||||
///
|
||||
/// Will return `Err` if command fifo could not be opened, read from, or written to
|
||||
pub fn set_selections<'a, I, S: 'a>(selections: I) -> Result<(), KakError>
|
||||
where
|
||||
I: IntoIterator<Item = S>,
|
||||
S: AsRef<str>,
|
||||
S: AsRef<str> + Clone + Display,
|
||||
{
|
||||
let mut selections_iter = selections.into_iter().peekable();
|
||||
if selections_iter.peek().is_none() {
|
||||
@ -125,7 +142,7 @@ where
|
||||
let mut f = open_command_fifo()?;
|
||||
write!(f, "set-register '\"'")?;
|
||||
for i in selections_iter {
|
||||
write!(f, " '{}'", escape(i))?;
|
||||
write!(f, " '{}'", escape(i.as_ref()))?;
|
||||
}
|
||||
write!(f, "; execute-keys R;")?;
|
||||
f.flush()?;
|
||||
@ -135,7 +152,7 @@ where
|
||||
/// # Errors
|
||||
///
|
||||
/// Will return `Err` if command fifo could not be opened, read from, or written to
|
||||
pub fn set_selections_desc<'a, I, SD: 'a + std::fmt::Display>(selections: I) -> Result<(), KakError>
|
||||
pub fn set_selections_desc<'a, I, SD: 'a + Display>(selections: I) -> Result<(), KakError>
|
||||
where
|
||||
I: IntoIterator<Item = SD>,
|
||||
SD: AsRef<SelectionDesc>,
|
||||
@ -158,11 +175,11 @@ where
|
||||
/// # Errors
|
||||
///
|
||||
/// Will return `Err` if command fifo could not be opened, read from, or written to
|
||||
pub fn display_message<S: AsRef<str>>(
|
||||
pub fn display_message<S: AsRef<str> + Clone + Display>(
|
||||
message: S,
|
||||
debug_message: Option<S>,
|
||||
) -> Result<(), KakError> {
|
||||
let msg_str = escape(message);
|
||||
let msg_str = escape(message.as_ref());
|
||||
{
|
||||
let mut f = open_command_fifo()?;
|
||||
|
||||
@ -170,15 +187,35 @@ 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 '{}';", escape(debug_msg_str))?;
|
||||
write!(f, "echo -debug '{}';", escape(debug_msg_str.as_ref()))?;
|
||||
}
|
||||
f.flush()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn escape<S: AsRef<str>>(s: S) -> String {
|
||||
s.as_ref().replace('\'', "''")
|
||||
/// Escapes a string to be sent to kak by replacing single tick with two single tics
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use kakplugin::escape;
|
||||
/// use std::borrow::Cow;
|
||||
///
|
||||
/// assert_eq!(escape("abcd"), "abcd");
|
||||
/// assert_eq!(escape("'ab\\cd'"), "''ab\\cd''");
|
||||
///
|
||||
/// // Will not reallocate for
|
||||
/// assert!(matches!(escape("abcd"), Cow::Borrowed(_)));
|
||||
/// assert!(matches!(escape("ab\\nc\nd"), Cow::Borrowed(_)));
|
||||
/// assert!(matches!(escape("ab'cd"), Cow::Owned(_)));
|
||||
/// ```
|
||||
pub fn escape(s: &str) -> Cow<str> {
|
||||
if s.contains('\'') {
|
||||
Cow::Owned(s.replace('\'', "''"))
|
||||
} else {
|
||||
Cow::Borrowed(s)
|
||||
}
|
||||
}
|
||||
|
||||
/// # Errors
|
||||
@ -221,7 +258,7 @@ where
|
||||
execute-keys '{}';
|
||||
echo -quoting shell -to-file {response_fifo} -- {};
|
||||
}}"#,
|
||||
escape(keys),
|
||||
escape(keys.as_ref()),
|
||||
msg.as_ref()
|
||||
),
|
||||
})?;
|
||||
|
@ -91,14 +91,14 @@ impl SelectionDesc {
|
||||
if self.left < self.right {
|
||||
// left anchor is first
|
||||
Self {
|
||||
left: self.left.clone(),
|
||||
right: self.right.clone(),
|
||||
left: self.left,
|
||||
right: self.right,
|
||||
}
|
||||
} else {
|
||||
// right anchor is first
|
||||
Self {
|
||||
left: self.right.clone(),
|
||||
right: self.left.clone(),
|
||||
left: self.right,
|
||||
right: self.left,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -269,15 +269,15 @@ impl From<&SelectionDesc> for SelectionDesc {
|
||||
impl From<&AnchorPosition> for SelectionDesc {
|
||||
fn from(ap: &AnchorPosition) -> Self {
|
||||
Self {
|
||||
left: ap.clone(),
|
||||
right: ap.clone(),
|
||||
left: *ap,
|
||||
right: *ap,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<SelectionDesc> for SelectionDesc {
|
||||
fn as_ref(&self) -> &Self {
|
||||
&self
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@ -592,7 +592,7 @@ impl Register {
|
||||
|
||||
impl AsRef<Register> for Register {
|
||||
fn as_ref(&self) -> &Self {
|
||||
&self
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -126,12 +126,12 @@ fn boxed_selections(options: &Options) -> Result<Vec<SelectionDesc>, KakError> {
|
||||
.collect::<Vec<SelectionDesc>>())
|
||||
}
|
||||
|
||||
/// Returns a vec of selections_desc of the intersection of the bounding box and the component rows
|
||||
/// Returns a vec of `selections_desc` of the intersection of the bounding box and the component rows
|
||||
///
|
||||
/// This function takes a selection desc, and its whole-row split selections (`<a-x><a-s>`).
|
||||
/// For each whole-row (col 1 to max col) selection, it finds the intersection between the min col and max col in `selection_desc`
|
||||
///
|
||||
/// * `selection_desc` - The base (possibly multiline) selection_desc
|
||||
/// * `selection_desc` - The base (possibly multiline) `selection_desc`
|
||||
/// * `selections_desc_rows` - Vec of above `selection_desc` split by line (`<a-x><a-s>`)
|
||||
fn to_boxed_selections<SD1, SD2>(
|
||||
selection_desc: SD1,
|
||||
@ -154,7 +154,7 @@ where
|
||||
|
||||
selections_desc_rows
|
||||
.iter()
|
||||
.map(|split_sd| {
|
||||
.filter_map(|split_sd| {
|
||||
// Find the intersection of <row>.<min_col>,<row>.<max_col>
|
||||
// If empty, return none. Flatten will not add it to the resulting vec
|
||||
split_sd.as_ref().intersect(SelectionDesc {
|
||||
@ -168,7 +168,6 @@ where
|
||||
},
|
||||
})
|
||||
})
|
||||
.flatten()
|
||||
.collect()
|
||||
}
|
||||
|
||||
|
10
src/incr.rs
10
src/incr.rs
@ -1,6 +1,6 @@
|
||||
use evalexpr::{eval, Value};
|
||||
use kakplugin::{get_selections, open_command_fifo, set_selections, KakError, Selection};
|
||||
use std::io::Write;
|
||||
use std::{borrow::Cow, io::Write};
|
||||
|
||||
#[derive(clap::StructOpt, Debug)]
|
||||
pub struct Options {
|
||||
@ -19,14 +19,14 @@ pub fn incr(options: &Options, should_increment: bool) -> Result<String, KakErro
|
||||
if should_increment { "+" } else { "-" },
|
||||
options.amount
|
||||
)) {
|
||||
Ok(Value::Float(f)) => f.to_string(),
|
||||
Ok(Value::Int(f)) => f.to_string(),
|
||||
Ok(_) => String::from(""),
|
||||
Ok(Value::Float(f)) => Cow::Owned(f.to_string()),
|
||||
Ok(Value::Int(f)) => Cow::Owned(f.to_string()),
|
||||
Ok(_) => Cow::Borrowed(""),
|
||||
Err(e) => {
|
||||
eprintln!("Error: {:?}", e);
|
||||
err_count = err_count.saturating_add(1);
|
||||
// Set the selection to empty
|
||||
String::from("")
|
||||
Cow::Borrowed("")
|
||||
}
|
||||
}
|
||||
}))?;
|
||||
|
@ -42,7 +42,7 @@ pub fn invert(options: &Options) -> Result<String, KakError> {
|
||||
get_selections_desc(Some(whole_document_selection_command))?
|
||||
.into_iter()
|
||||
// dd - The full row selectiondesc, spanning from col 1 to the rightmost col, for every row in the file
|
||||
.map(|dd: SelectionDesc| {
|
||||
.flat_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))
|
||||
@ -57,7 +57,6 @@ pub fn invert(options: &Options) -> Result<String, KakError> {
|
||||
}
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.collect()
|
||||
};
|
||||
|
||||
@ -68,11 +67,11 @@ pub fn invert(options: &Options) -> Result<String, KakError> {
|
||||
Ok(format!("Inverted {} selections", count_selections))
|
||||
}
|
||||
|
||||
/// Subtract an iterator of `SelectionDesc`s from a given SelectionDesc
|
||||
/// 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
|
||||
/// * `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<SD1, SD2>(
|
||||
selection_desc: SD1,
|
||||
@ -101,11 +100,11 @@ where
|
||||
// TODO: Replace Just with JustLeft and JustRight?
|
||||
rightmost_selection_desc = sd.as_ref().clone();
|
||||
}
|
||||
MaybeSplit::JustTwo(sda, sdb) => {
|
||||
MaybeSplit::JustTwo(selectiondesc_a, selectiondesc_b) => {
|
||||
// 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;
|
||||
ret.push(selectiondesc_a);
|
||||
rightmost_selection_desc = selectiondesc_b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ mod errors;
|
||||
mod incr;
|
||||
mod invert;
|
||||
mod math_eval;
|
||||
mod pad;
|
||||
mod set;
|
||||
mod shuf;
|
||||
mod sort;
|
||||
@ -55,6 +56,8 @@ enum Commands {
|
||||
Invert(invert::Options),
|
||||
#[clap(about = "Evaluate selections as a math expression", visible_aliases = &["bc", "eval"])]
|
||||
MathEval(math_eval::Options),
|
||||
#[clap(about = "Pad all selections by some specifier")]
|
||||
Pad(pad::Options),
|
||||
#[clap(about = "Trim every selection")]
|
||||
Trim(trim::Options),
|
||||
#[clap(about = "Perform set operations on selections")]
|
||||
@ -78,9 +81,8 @@ fn main() {
|
||||
let args = env::args().collect::<Vec<_>>();
|
||||
eprintln!("Len: {}, args: {:?}", args.len(), args);
|
||||
if args.len() >= 2 && args[1] == "shell-script-candidates" {
|
||||
match kakplugin::generate_shell_script_candidates(Commands::VARIANTS) {
|
||||
Err(e) => eprintln!("{e:?}"),
|
||||
Ok(()) => {}
|
||||
if let Err(e) = kakplugin::generate_shell_script_candidates(Commands::VARIANTS) {
|
||||
eprintln!("{e:?}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -115,6 +117,7 @@ fn run() -> Result<String, KakError> {
|
||||
Commands::Uniq(o) => uniq::uniq(o),
|
||||
Commands::Invert(o) => invert::invert(o),
|
||||
Commands::MathEval(o) => math_eval::math_eval(o),
|
||||
Commands::Pad(o) => pad::pad(o),
|
||||
Commands::Trim(o) => trim::trim(o),
|
||||
Commands::Set(o) => set::set(o),
|
||||
// Commands::Xargs(o) => xargs::xargs(o),
|
||||
|
@ -1,6 +1,6 @@
|
||||
use evalexpr::{eval, Value};
|
||||
use kakplugin::{get_selections, open_command_fifo, set_selections, KakError, Selection};
|
||||
use std::io::Write;
|
||||
use std::{borrow::Cow, io::Write};
|
||||
|
||||
#[derive(clap::StructOpt, Debug)]
|
||||
pub struct Options;
|
||||
@ -10,14 +10,14 @@ pub fn math_eval(_options: &Options) -> Result<String, KakError> {
|
||||
let selections = get_selections(None)?;
|
||||
|
||||
set_selections(selections.iter().map(|s| match eval(s) {
|
||||
Ok(Value::Float(f)) => f.to_string(),
|
||||
Ok(Value::Int(f)) => f.to_string(),
|
||||
Ok(_) => String::from(""),
|
||||
Ok(Value::Float(f)) => Cow::Owned(f.to_string()),
|
||||
Ok(Value::Int(f)) => Cow::Owned(f.to_string()),
|
||||
Ok(_) => Cow::Borrowed(""),
|
||||
Err(e) => {
|
||||
eprintln!("Error: {:?}", e);
|
||||
err_count = err_count.saturating_add(1);
|
||||
// Set the selection to empty
|
||||
String::from("")
|
||||
Cow::Borrowed("")
|
||||
}
|
||||
}))?;
|
||||
|
||||
|
55
src/pad.rs
Normal file
55
src/pad.rs
Normal file
@ -0,0 +1,55 @@
|
||||
use crate::utils::split_newlines;
|
||||
use evalexpr::{eval, Value};
|
||||
use kakplugin::{get_selections, open_command_fifo, set_selections, KakError, Selection};
|
||||
use std::{borrow::Cow, io::Write};
|
||||
|
||||
#[derive(clap::StructOpt, Debug)]
|
||||
pub struct Options {
|
||||
#[clap(index = 1, help = "Pad with this char", default_value = "0")]
|
||||
fill: char,
|
||||
#[clap(short, long, help = "Pad on the right instead of the left")]
|
||||
right: bool,
|
||||
}
|
||||
|
||||
pub fn pad(options: &Options) -> Result<String, KakError> {
|
||||
let selections = get_selections(None)?;
|
||||
let selections_trailing_split: Vec<(&str, &str, &str)> = selections
|
||||
.iter()
|
||||
// We don't want leading or trailing newlines to count
|
||||
.map(|s| split_newlines(s))
|
||||
.collect();
|
||||
|
||||
// The max length of selections with newlines split off
|
||||
let max_len = selections_trailing_split
|
||||
.iter()
|
||||
.map(|(_, s, _)| s.len())
|
||||
.max()
|
||||
.ok_or(KakError::CustomStatic("No selections"))?;
|
||||
|
||||
let mut num_padded: usize = 0;
|
||||
let num_total = selections.len();
|
||||
|
||||
set_selections(selections_trailing_split.iter().zip(selections.iter()).map(
|
||||
|((leading_newlines, s, trailing_newlines), orig_s)| match max_len.checked_sub(s.len()) {
|
||||
Some(0) | None => Cow::Borrowed(orig_s.as_str()),
|
||||
Some(len) => {
|
||||
num_padded += 1;
|
||||
let fill = options.fill.to_string().repeat(len);
|
||||
let mut ret = leading_newlines.to_string();
|
||||
if options.right {
|
||||
ret.push_str(s);
|
||||
ret.push_str(&fill);
|
||||
} else {
|
||||
ret.push_str(&fill);
|
||||
ret.push_str(s);
|
||||
}
|
||||
ret.push_str(trailing_newlines);
|
||||
Cow::Owned(ret)
|
||||
}
|
||||
},
|
||||
))?;
|
||||
|
||||
Ok(format!(
|
||||
"Padded {num_padded} selections ({num_total} total)",
|
||||
))
|
||||
}
|
29
src/set.rs
29
src/set.rs
@ -1,8 +1,8 @@
|
||||
// use crate::utils;
|
||||
use clap::ArgEnum;
|
||||
use kakplugin::{
|
||||
get_selections, get_selections_with_desc, set_selections, set_selections_desc, types::Register,
|
||||
KakError, Selection, SelectionWithDesc,
|
||||
get_selections, get_selections_with_desc_ordered, set_selections, set_selections_desc,
|
||||
types::Register, KakError, Selection, SelectionWithDesc,
|
||||
};
|
||||
use linked_hash_map::LinkedHashMap;
|
||||
use linked_hash_set::LinkedHashSet;
|
||||
@ -14,6 +14,7 @@ pub struct Options {
|
||||
#[clap(
|
||||
min_values = 1,
|
||||
max_values = 3,
|
||||
allow_hyphen_values = true,
|
||||
help = "Register operation and operand. Empty register is current selection. Example: 'a-b' or '+b'"
|
||||
)]
|
||||
args: Vec<String>,
|
||||
@ -114,8 +115,8 @@ pub fn set(options: &Options) -> Result<String, KakError> {
|
||||
|
||||
match &operation {
|
||||
Operation::Compare => compare(
|
||||
&left_register,
|
||||
&right_register,
|
||||
left_register,
|
||||
right_register,
|
||||
&result,
|
||||
&left_ordered_counts,
|
||||
&right_ordered_counts,
|
||||
@ -127,11 +128,11 @@ pub fn set(options: &Options) -> Result<String, KakError> {
|
||||
if left_register == Register::Underscore {
|
||||
// If the user asked for an intersection or subtraction from the current selection, we can update selection_descs only
|
||||
// For example (current selection) - (contents of register a) allows us to simply deselect some selections
|
||||
reduce_selections(&options, &result)?
|
||||
reduce_selections(options, &result)?;
|
||||
} else {
|
||||
// The user asked for registers that *aren't* the current selection
|
||||
// This means either registers don't represent the current selection, or the current selection is on the other side
|
||||
print_result(&result)?
|
||||
print_result(&result)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -148,18 +149,14 @@ pub fn set(options: &Options) -> Result<String, KakError> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Reduces selections to those that are in the key_set_operation_result
|
||||
/// Reduces selections to those that are in the `key_set_operation_result`
|
||||
fn reduce_selections(
|
||||
options: &Options,
|
||||
key_set_operation_result: &LinkedHashSet<&Selection>,
|
||||
) -> Result<(), KakError> {
|
||||
// The registers should have been read in a draft context
|
||||
// So the current selection will be unmodified
|
||||
let selections_with_desc = {
|
||||
let mut r = get_selections_with_desc(None)?;
|
||||
r.sort_by_key(|s| s.desc.sort());
|
||||
r
|
||||
};
|
||||
let selections_with_desc = get_selections_with_desc_ordered(None)?;
|
||||
|
||||
set_selections_desc(selections_with_desc.into_iter().filter_map(|swd| {
|
||||
// Does not matter if the operation was - or &
|
||||
@ -212,8 +209,8 @@ fn print_result(key_set_operation_result: &LinkedHashSet<&Selection>) -> Result<
|
||||
}
|
||||
|
||||
fn compare(
|
||||
left_register: &Register,
|
||||
right_register: &Register,
|
||||
left_register: Register,
|
||||
right_register: Register,
|
||||
key_set_operation_result: &LinkedHashSet<&Selection>,
|
||||
left_ordered_counts: &LinkedHashMap<Selection, usize>,
|
||||
right_ordered_counts: &LinkedHashMap<Selection, usize>,
|
||||
@ -281,11 +278,11 @@ fn to_ordered_counts(options: &Options, sels: Vec<Selection>) -> LinkedHashMap<S
|
||||
if key.is_empty() {
|
||||
// We don't want to even pretend to look at empty keys
|
||||
continue;
|
||||
} else {
|
||||
}
|
||||
|
||||
let entry: &mut usize = ret.entry(key).or_insert(0);
|
||||
*entry = entry.saturating_add(1);
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
|
79
src/sort.rs
79
src/sort.rs
@ -1,7 +1,7 @@
|
||||
use alphanumeric_sort::compare_str;
|
||||
use kakplugin::{self, get_selections_with_desc, open_command_fifo, KakError, SelectionWithDesc};
|
||||
use regex::Regex;
|
||||
use std::{cmp::Ordering, io::Write};
|
||||
use std::{borrow::Cow, cmp::Ordering, io::Write};
|
||||
|
||||
#[derive(clap::StructOpt, Debug)]
|
||||
pub struct Options {
|
||||
@ -16,10 +16,11 @@ pub struct Options {
|
||||
// TODO: Can we invert a boolean? This name is terrible
|
||||
#[clap(short = 'S', long, parse(try_from_str = invert_bool), default_value_t, help = "Do not treat trimmed value of selections when sorting")]
|
||||
no_skip_whitespace: bool,
|
||||
#[clap(short, long, help = "Sort numbers lexicographically")]
|
||||
lexicographic_sort: bool,
|
||||
#[clap(short = 'L', long, help = "Do not sort numbers lexicographically")]
|
||||
no_lexicographic_sort: bool,
|
||||
#[clap(short, long, help = "Reverse sorting")]
|
||||
reverse: bool,
|
||||
// TODO: Allow ignoring case
|
||||
#[clap(short, long, help = "Ignore case when sorting")]
|
||||
ignore_case: bool,
|
||||
}
|
||||
@ -37,7 +38,7 @@ struct SortableSelection<'a> {
|
||||
/// The content of the selection
|
||||
selection: &'a SelectionWithDesc,
|
||||
/// The string used to compare the content
|
||||
content_comparison: &'a str,
|
||||
content_comparison: Cow<'a, str>,
|
||||
/// Any subselections
|
||||
subselections: Vec<&'a str>,
|
||||
}
|
||||
@ -105,19 +106,18 @@ fn to_sortable_selection<'a, 'b>(
|
||||
selection: &'a SelectionWithDesc,
|
||||
options: &'b Options,
|
||||
) -> SortableSelection<'a> {
|
||||
if options.no_skip_whitespace {
|
||||
SortableSelection {
|
||||
selection,
|
||||
content_comparison: selection.content.as_str(),
|
||||
// TODO: Properly use Cow
|
||||
content_comparison: crate::utils::get_key(
|
||||
&selection.content,
|
||||
!options.no_skip_whitespace,
|
||||
options.regex.as_ref(),
|
||||
options.ignore_case,
|
||||
)
|
||||
.into(),
|
||||
subselections: vec![],
|
||||
}
|
||||
} else {
|
||||
SortableSelection {
|
||||
selection,
|
||||
content_comparison: selection.content.as_str().trim(),
|
||||
subselections: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sort(options: &Options) -> Result<String, KakError> {
|
||||
@ -147,25 +147,31 @@ pub fn sort(options: &Options) -> Result<String, KakError> {
|
||||
.map(|s| to_sortable_selection(s, options))
|
||||
.collect()
|
||||
}
|
||||
(Some(regex), None) => {
|
||||
(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
|
||||
})
|
||||
.map(|s| to_sortable_selection(s, options))
|
||||
.collect()
|
||||
|
||||
// TODO: Figure out if this is fine
|
||||
// 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
|
||||
@ -178,22 +184,23 @@ pub fn sort(options: &Options) -> Result<String, KakError> {
|
||||
zipped.sort_by(|a, b| {
|
||||
// First, try sorting by subselection. This won't iterate anything if either is None (regex and default mode)
|
||||
for (a_subselection, b_subselection) in a.subselections.iter().zip(b.subselections.iter()) {
|
||||
let comparison = if options.lexicographic_sort {
|
||||
let comparison = if options.no_lexicographic_sort {
|
||||
a_subselection.cmp(b_subselection)
|
||||
} else {
|
||||
compare_str(a_subselection, b_subselection)
|
||||
compare_str(&a_subselection, &b_subselection)
|
||||
};
|
||||
match comparison {
|
||||
Ordering::Less | Ordering::Greater => return comparison,
|
||||
Ordering::Equal => {}
|
||||
|
||||
// If the comparison is not equal, stop here
|
||||
if comparison != Ordering::Equal {
|
||||
return comparison;
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, default to comparing the content
|
||||
if options.lexicographic_sort {
|
||||
a.content_comparison.cmp(b.content_comparison)
|
||||
if options.no_lexicographic_sort {
|
||||
a.content_comparison.cmp(&b.content_comparison)
|
||||
} else {
|
||||
compare_str(a.content_comparison, b.content_comparison)
|
||||
compare_str(&a.content_comparison, &b.content_comparison)
|
||||
}
|
||||
});
|
||||
|
||||
|
42
src/utils.rs
42
src/utils.rs
@ -52,3 +52,45 @@ pub fn get_hash(
|
||||
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
/// Splits an `&str` into (string_value, trailing_newlines)
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// assert_eq!(split_trailing_newlines("asdf\n"), ("asdf", "\n"));
|
||||
/// assert_eq!(split_trailing_newlines("asdf\n\nhjk\n"), ("asdf\n\nhjk", "\n"));
|
||||
/// assert_eq!(split_trailing_newlines("asdf"), ("asdf", ""));
|
||||
/// assert_eq!(split_trailing_newlines(""), ("", ""));
|
||||
/// ```
|
||||
pub fn split_trailing_newlines<'a>(s: &'a str) -> (&'a str, &'a str) {
|
||||
s.rfind(|c| c != '\n')
|
||||
.map(|idx| s.split_at(idx + 1))
|
||||
.unwrap_or((s, ""))
|
||||
}
|
||||
|
||||
/// Splits an `&str` into (leading_newlines, string_value, trailing_newlines)
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// assert_eq!(split_newlines("asdf\n"), ("", "asdf", "\n"));
|
||||
/// assert_eq!(split_newlines("asdf\n\nhjk\n"), ("", "asdf\n\nhjk", "\n"));
|
||||
/// assert_eq!(split_newlines("\nasdf\n\nhjk\n"), ("\n", "asdf\n\nhjk", "\n"));
|
||||
/// assert_eq!(split_newlines("asdf"), ("", "asdf", ""));
|
||||
/// assert_eq!(split_newlines("\n\n\nasdf"), ("\n\n\n", "asdf", ""));
|
||||
/// assert_eq!(split_newlines(""), ("", "", ""));
|
||||
/// ```
|
||||
pub fn split_newlines<'a>(s: &'a str) -> (&'a str, &'a str, &'a str) {
|
||||
let (leading_newlines, s) = s
|
||||
.find(|c| c != '\n')
|
||||
.map(|idx| s.split_at(idx))
|
||||
.unwrap_or(("", s));
|
||||
|
||||
let (s, trailing_newlines) = s
|
||||
.rfind(|c| c != '\n')
|
||||
.map(|idx| s.split_at(idx + 1))
|
||||
.unwrap_or((s, ""));
|
||||
|
||||
(leading_newlines, s, trailing_newlines)
|
||||
}
|
||||
|
@ -20,24 +20,22 @@ pub struct Options {
|
||||
}
|
||||
pub fn xlookup(options: &Options) -> Result<String, KakError> {
|
||||
let lookup_table = build_lookuptable(options.register)?;
|
||||
eprintln!("Lookup table: {lookup_table:#?}");
|
||||
|
||||
let selections = get_selections(None)?;
|
||||
|
||||
let mut err_count: usize = 0;
|
||||
|
||||
set_selections(selections.iter().map(|key| {
|
||||
match lookup_table.get(&get_hash(&key, false, None, false)) {
|
||||
Some(v) => v.to_string(),
|
||||
None => {
|
||||
eprintln!(
|
||||
"Nothing for '{key}' ({})",
|
||||
get_hash(&key, false, None, false)
|
||||
);
|
||||
lookup_table
|
||||
.get(&get_hash(key, false, None, false))
|
||||
.map_or_else(
|
||||
|| {
|
||||
eprintln!("Key '{key}' not found",);
|
||||
err_count += 1;
|
||||
String::from("")
|
||||
}
|
||||
}
|
||||
},
|
||||
ToString::to_string,
|
||||
)
|
||||
}))?;
|
||||
|
||||
Ok(if err_count == 0 {
|
||||
@ -52,14 +50,14 @@ pub fn xlookup(options: &Options) -> Result<String, KakError> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn build_lookuptable(reg: Register) -> Result<BTreeMap<u64, Selection>, KakError> {
|
||||
let mut selections = get_selections(Some(&format!("\"{reg}z")))?;
|
||||
fn build_lookuptable(register: Register) -> Result<BTreeMap<u64, Selection>, KakError> {
|
||||
let mut selections = get_selections(Some(&format!("\"{register}z")))?;
|
||||
let mut iter = selections.array_chunks_mut();
|
||||
let ret = iter.try_fold(BTreeMap::new(), |mut acc, [key, value]| {
|
||||
match acc.entry(get_hash(&key, false, None, false)) {
|
||||
match acc.entry(get_hash(key, false, None, false)) {
|
||||
Occupied(_) => Err(KakError::Custom(format!("Duplicate key '{key}'"))),
|
||||
Vacant(v) => {
|
||||
v.insert(value.to_owned());
|
||||
v.insert(value.clone());
|
||||
Ok(acc)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user