diff --git a/README.adoc b/README.adoc index bc019b7..5195e88 100644 --- a/README.adoc +++ b/README.adoc @@ -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 diff --git a/kakplugin/src/lib.rs b/kakplugin/src/lib.rs index ca476aa..187797a 100644 --- a/kakplugin/src/lib.rs +++ b/kakplugin/src/lib.rs @@ -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(keys: Option) -> Result, KakError> +pub fn get_selections_desc_unordered(keys: Option) -> Result, KakError> where S: AsRef, { @@ -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, 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, _>>() } +/// 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, 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, - S: AsRef, + S: AsRef + 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, SD: AsRef, @@ -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>( +pub fn display_message + Clone + Display>( message: S, debug_message: Option, ) -> 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>( 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: 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 { + 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() ), })?; diff --git a/kakplugin/src/types.rs b/kakplugin/src/types.rs index b433bb5..6dcabd8 100644 --- a/kakplugin/src/types.rs +++ b/kakplugin/src/types.rs @@ -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 for SelectionDesc { fn as_ref(&self) -> &Self { - &self + self } } @@ -592,7 +592,7 @@ impl Register { impl AsRef for Register { fn as_ref(&self) -> &Self { - &self + self } } diff --git a/src/box_.rs b/src/box_.rs index 6ec2831..e565a81 100644 --- a/src/box_.rs +++ b/src/box_.rs @@ -126,12 +126,12 @@ fn boxed_selections(options: &Options) -> Result, KakError> { .collect::>()) } -/// 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 (``). /// 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 (``) fn to_boxed_selections( selection_desc: SD1, @@ -154,7 +154,7 @@ where selections_desc_rows .iter() - .map(|split_sd| { + .filter_map(|split_sd| { // Find the intersection of .,. // 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() } diff --git a/src/incr.rs b/src/incr.rs index 6215927..90bf09a 100644 --- a/src/incr.rs +++ b/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 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("") } } }))?; diff --git a/src/invert.rs b/src/invert.rs index c9deada..1953fdd 100644 --- a/src/invert.rs +++ b/src/invert.rs @@ -42,7 +42,7 @@ pub fn invert(options: &Options) -> Result { 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 { } } }) - .flatten() .collect() }; @@ -68,11 +67,11 @@ pub fn invert(options: &Options) -> Result { 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( 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; } } } diff --git a/src/main.rs b/src/main.rs index d84003a..2228d0a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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::>(); 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 { 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), diff --git a/src/math_eval.rs b/src/math_eval.rs index 5beb041..86c26d0 100644 --- a/src/math_eval.rs +++ b/src/math_eval.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; @@ -10,14 +10,14 @@ pub fn math_eval(_options: &Options) -> Result { 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("") } }))?; diff --git a/src/pad.rs b/src/pad.rs new file mode 100644 index 0000000..1f0373b --- /dev/null +++ b/src/pad.rs @@ -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 { + 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)", + )) +} diff --git a/src/set.rs b/src/set.rs index d6f9c61..0817af9 100644 --- a/src/set.rs +++ b/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, @@ -114,8 +115,8 @@ pub fn set(options: &Options) -> Result { 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 { 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 { }) } -/// 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, right_ordered_counts: &LinkedHashMap, @@ -281,10 +278,10 @@ fn to_ordered_counts(options: &Options, sels: Vec) -> LinkedHashMap { /// 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,18 +106,17 @@ 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(), - subselections: vec![], - } - } else { - SortableSelection { - selection, - content_comparison: selection.content.as_str().trim(), - subselections: vec![], - } + SortableSelection { + selection, + // 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![], } } @@ -147,25 +147,31 @@ pub fn sort(options: &Options) -> Result { .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 { 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) } }); diff --git a/src/utils.rs b/src/utils.rs index d1de8b4..1124de5 100644 --- a/src/utils.rs +++ b/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) +} diff --git a/src/xlookup.rs b/src/xlookup.rs index 2d2daf9..298dfee 100644 --- a/src/xlookup.rs +++ b/src/xlookup.rs @@ -20,24 +20,22 @@ pub struct Options { } pub fn xlookup(options: &Options) -> Result { 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) - ); - err_count += 1; - String::from("") - } - } + 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 { }) } -pub fn build_lookuptable(reg: Register) -> Result, KakError> { - let mut selections = get_selections(Some(&format!("\"{reg}z")))?; +fn build_lookuptable(register: Register) -> Result, 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) } }