Start splitting code into module
This commit is contained in:
parent
42c20e6f1e
commit
3d867c7f0d
8
Cargo.lock
generated
8
Cargo.lock
generated
@ -124,6 +124,13 @@ dependencies = [
|
|||||||
"hashbrown",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "kakplugin"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"shellwords",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kakutils-rs"
|
name = "kakutils-rs"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -131,6 +138,7 @@ dependencies = [
|
|||||||
"alphanumeric-sort",
|
"alphanumeric-sort",
|
||||||
"clap",
|
"clap",
|
||||||
"evalexpr",
|
"evalexpr",
|
||||||
|
"kakplugin",
|
||||||
"rand",
|
"rand",
|
||||||
"regex",
|
"regex",
|
||||||
"shellwords",
|
"shellwords",
|
||||||
|
22
Cargo.toml
22
Cargo.toml
@ -1,3 +1,5 @@
|
|||||||
|
# syntax = denzp/cargo-wharf-frontend
|
||||||
|
|
||||||
# cargo-features = ["strip"]
|
# cargo-features = ["strip"]
|
||||||
[package]
|
[package]
|
||||||
name = "kakutils-rs"
|
name = "kakutils-rs"
|
||||||
@ -7,6 +9,25 @@ license = "MIT"
|
|||||||
readme = "README.adoc"
|
readme = "README.adoc"
|
||||||
keywords = ["cli", "kakoune"]
|
keywords = ["cli", "kakoune"]
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
members = [
|
||||||
|
".",
|
||||||
|
"./kakplugin/",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package.metadata.wharf.binary]]
|
||||||
|
name = "kakutils-rs"
|
||||||
|
destination = "/bin/kakutils-rs"
|
||||||
|
|
||||||
|
[package.metadata.wharf.builder]
|
||||||
|
# image = "rust"
|
||||||
|
# image = "ekidd/rust-musl-builder"
|
||||||
|
image = "clux/muslrust:nightly-2021-03-02"
|
||||||
|
target = "x86_64-unknown-linux-musl"
|
||||||
|
|
||||||
|
[package.metadata.wharf.output]
|
||||||
|
image = "scratch"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
@ -17,6 +38,7 @@ alphanumeric-sort = "1"
|
|||||||
shellwords = "1"
|
shellwords = "1"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
evalexpr = "7"
|
evalexpr = "7"
|
||||||
|
kakplugin = {path = "./kakplugin/"}
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
|
9
kakplugin/Cargo.toml
Normal file
9
kakplugin/Cargo.toml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
[package]
|
||||||
|
name = "kakplugin"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
shellwords = "1"
|
32
kakplugin/src/errors.rs
Normal file
32
kakplugin/src/errors.rs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
use std::num::ParseIntError;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum KakError {
|
||||||
|
/// A required environment variable was not set
|
||||||
|
EnvVarNotSet(String),
|
||||||
|
/// An environment variable was not parsable in unicode
|
||||||
|
EnvVarUnicode(String),
|
||||||
|
/// There was an error parsing a response from kak
|
||||||
|
Parse(String),
|
||||||
|
/// There was an error with a response kak gave
|
||||||
|
KakResponse(String),
|
||||||
|
/// IO Error
|
||||||
|
Io(std::io::Error),
|
||||||
|
}
|
||||||
|
impl From<std::io::Error> for KakError {
|
||||||
|
fn from(e: std::io::Error) -> Self {
|
||||||
|
Self::Io(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<shellwords::MismatchedQuotes> for KakError {
|
||||||
|
fn from(e: shellwords::MismatchedQuotes) -> Self {
|
||||||
|
Self::Parse(format!("Shell could not be parsed: {e:?}"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ParseIntError> for KakError {
|
||||||
|
fn from(e: ParseIntError) -> Self {
|
||||||
|
Self::Parse(format!("Could not parse as integer: {e:?}"))
|
||||||
|
}
|
||||||
|
}
|
198
kakplugin/src/lib.rs
Normal file
198
kakplugin/src/lib.rs
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
mod errors;
|
||||||
|
mod types;
|
||||||
|
pub use errors::KakError;
|
||||||
|
use std::{
|
||||||
|
env, fmt,
|
||||||
|
fs::{self, File, OpenOptions},
|
||||||
|
io::{BufWriter, Write},
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
|
pub use types::{
|
||||||
|
AnchorPosition, Selection, SelectionDesc, SelectionWithDesc, SelectionWithSubselections,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Will return `Err` if command fifo could not be opened, read from, or written to
|
||||||
|
pub fn get_selections() -> Result<Vec<Selection>, KakError> {
|
||||||
|
response("%val{selections}")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Will return `Err` if command fifo could not be opened, read from, or written to
|
||||||
|
pub fn get_selections_desc() -> Result<Vec<SelectionDesc>, KakError> {
|
||||||
|
response("%val{selections_desc}")?
|
||||||
|
.iter()
|
||||||
|
.map(|sd| SelectionDesc::from_str(sd))
|
||||||
|
.collect::<Result<Vec<_>, KakError>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
// pub fn get_selections_with_subselections(
|
||||||
|
// register: &str,
|
||||||
|
// ) -> Result<Vec<SelectionWithSubselections>, KakError> {
|
||||||
|
// // TODO: Escape register
|
||||||
|
// let subselections = get_selections_with_desc()?;
|
||||||
|
// exec(format!("\"{}z", register.replace('\'', "''")))?;
|
||||||
|
// let selections = get_selections_with_desc()?;
|
||||||
|
|
||||||
|
// for sel in selections {
|
||||||
|
// for i in subselections {}
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
/// # 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() -> Result<Vec<SelectionWithDesc>, KakError> {
|
||||||
|
let mut selections = get_selections()?;
|
||||||
|
let selections_desc = get_selections_desc()?;
|
||||||
|
|
||||||
|
if selections.len() != selections_desc.len() {
|
||||||
|
return Err(KakError::KakResponse(format!(
|
||||||
|
"When requesting selections (={}) and selections_desc (={}), their count did not match",
|
||||||
|
selections.len(),
|
||||||
|
selections_desc.len()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let min_selection = selections_desc.iter().min().ok_or_else(|| {
|
||||||
|
KakError::KakResponse(format!(
|
||||||
|
"Selections are empty, which should not be possible"
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Kakoune prints selections in file order, but prints selections_desc rotated based on current selection
|
||||||
|
// Ex:
|
||||||
|
// [a] [b] (c) [d] where () is primary selection
|
||||||
|
// selections: a b c d
|
||||||
|
// selections_desc: c d a b
|
||||||
|
|
||||||
|
// Need to rotate selections by primary selection's position in the list
|
||||||
|
match selections_desc.iter().position(|p| p == min_selection) {
|
||||||
|
Some(i) => {
|
||||||
|
selections.rotate_right(i);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
return Err(KakError::KakResponse(format!(
|
||||||
|
"Primary selection {} not found in selection_desc list ({:#?})",
|
||||||
|
min_selection, selections_desc
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selections
|
||||||
|
.into_iter()
|
||||||
|
.zip(selections_desc.into_iter())
|
||||||
|
.map(|(content, desc)| Ok(SelectionWithDesc { content, desc }))
|
||||||
|
.collect::<Result<Vec<_>, _>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Will return `Err` if command fifo could not be opened, read from, or written to
|
||||||
|
pub fn set_selections<'a, I, S: 'a + ?Sized>(selections: I) -> Result<(), KakError>
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = &'a S>,
|
||||||
|
S: AsRef<str> + fmt::Display,
|
||||||
|
{
|
||||||
|
let mut f = open_command_fifo()?;
|
||||||
|
write!(f, "reg '\"'")?;
|
||||||
|
for i in selections {
|
||||||
|
write!(f, " '{}'", i.as_ref().replace('\'', "''"))?;
|
||||||
|
}
|
||||||
|
write!(f, "; exec R;")?;
|
||||||
|
f.flush()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Will return `Err` if command fifo could not be opened, read from, or written to
|
||||||
|
pub fn set_selections_desc<'a, I>(selections: I) -> Result<(), KakError>
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = &'a SelectionDesc>,
|
||||||
|
{
|
||||||
|
let mut f = open_command_fifo()?;
|
||||||
|
write!(f, "select")?;
|
||||||
|
for i in selections {
|
||||||
|
write!(f, " {}", i)?;
|
||||||
|
}
|
||||||
|
write!(f, ";")?;
|
||||||
|
f.flush()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Will return `Err` if command fifo could not be opened, read from, or written to
|
||||||
|
pub fn send_message<S: AsRef<str>>(message: S, debug_message: Option<S>) -> Result<(), KakError> {
|
||||||
|
let msg_str = message.as_ref().replace('\'', "''");
|
||||||
|
{
|
||||||
|
let mut f = open_command_fifo()?;
|
||||||
|
|
||||||
|
write!(f, "echo '{}';", msg_str)?;
|
||||||
|
write!(f, "echo -debug '{}';", msg_str)?;
|
||||||
|
|
||||||
|
if let Some(debug_msg_str) = &debug_message.as_ref() {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"echo -debug '{}';",
|
||||||
|
debug_msg_str.as_ref().replace('\'', "''")
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
f.flush()?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Will return `Err` if command fifo could not be opened or written to
|
||||||
|
pub fn exec(cmd: &str) -> Result<(), KakError> {
|
||||||
|
let mut f = open_command_fifo()?;
|
||||||
|
|
||||||
|
write!(f, "{}", cmd)?;
|
||||||
|
f.flush().map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Will return `Err` if command fifo could not be opened or written to
|
||||||
|
pub fn response(msg: &str) -> Result<Vec<String>, KakError> {
|
||||||
|
exec(&format!(
|
||||||
|
"echo -quoting shell -to-file {} -- {msg}",
|
||||||
|
get_var("kak_response_fifo")?
|
||||||
|
))?;
|
||||||
|
|
||||||
|
let selections = shellwords::split(&fs::read_to_string(&get_var("kak_response_fifo")?)?)?;
|
||||||
|
|
||||||
|
Ok(selections)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Will return `Err` if command fifo could not be opened
|
||||||
|
pub fn open_command_fifo() -> Result<BufWriter<File>, KakError> {
|
||||||
|
OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.append(true)
|
||||||
|
.open(&get_var("kak_command_fifo")?)
|
||||||
|
.map(BufWriter::new)
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Will return `Err` if requested environment variable is not unicode or not present
|
||||||
|
fn get_var(var_name: &str) -> Result<String, KakError> {
|
||||||
|
env::var(var_name).map_err(|e| match e {
|
||||||
|
env::VarError::NotPresent => {
|
||||||
|
KakError::EnvVarNotSet(format!("Env var {} is not defined", var_name))
|
||||||
|
}
|
||||||
|
env::VarError::NotUnicode(_) => {
|
||||||
|
KakError::EnvVarUnicode(format!("Env var {} is not unicode", var_name))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
141
kakplugin/src/types.rs
Normal file
141
kakplugin/src/types.rs
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
use crate::KakError;
|
||||||
|
use std::{fmt, str::FromStr};
|
||||||
|
|
||||||
|
pub type Selection = String;
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Debug)]
|
||||||
|
pub struct SelectionWithDesc {
|
||||||
|
pub content: Selection,
|
||||||
|
pub desc: SelectionDesc,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Debug)]
|
||||||
|
pub struct SelectionWithSubselections {
|
||||||
|
pub selection: SelectionWithDesc,
|
||||||
|
pub subselections: Vec<SelectionWithDesc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, PartialOrd, Ord, Eq, Debug)]
|
||||||
|
pub struct SelectionDesc {
|
||||||
|
pub left: AnchorPosition,
|
||||||
|
pub right: AnchorPosition,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SelectionDesc {
|
||||||
|
#[must_use]
|
||||||
|
pub fn sort(&self) -> Self {
|
||||||
|
if self.left < self.right {
|
||||||
|
// left anchor is first
|
||||||
|
Self {
|
||||||
|
left: self.left.clone(),
|
||||||
|
right: self.right.clone(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// right anchor is first
|
||||||
|
Self {
|
||||||
|
left: self.right.clone(),
|
||||||
|
right: self.left.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn contains(&self, b: &Self) -> bool {
|
||||||
|
// Cursor and anchor can be flipped. Set a.0 to be leftmost cursor
|
||||||
|
let sorted_a = self.sort();
|
||||||
|
let sorted_b = b.sort();
|
||||||
|
|
||||||
|
sorted_b.left >= sorted_a.left && sorted_b.right <= sorted_a.right
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for SelectionDesc {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{},{}", self.left, self.right)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for SelectionDesc {
|
||||||
|
type Err = KakError;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let (left, right) = s
|
||||||
|
.split_once(',')
|
||||||
|
.ok_or_else(|| KakError::Parse(format!("Could not parse as position: {}", s)))?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
left: AnchorPosition::from_str(left)?,
|
||||||
|
right: AnchorPosition::from_str(right)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialOrd, PartialEq, Clone, Eq, Ord, Debug)]
|
||||||
|
pub struct AnchorPosition {
|
||||||
|
pub row: usize,
|
||||||
|
pub col: usize,
|
||||||
|
}
|
||||||
|
impl fmt::Display for AnchorPosition {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}.{}", self.row, self.col)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for AnchorPosition {
|
||||||
|
type Err = KakError;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let (left, right) = s
|
||||||
|
.split_once('.')
|
||||||
|
.ok_or_else(|| KakError::Parse(format!("Could not parse as position: {}", s)))?;
|
||||||
|
Ok(Self {
|
||||||
|
row: usize::from_str(left)?,
|
||||||
|
col: usize::from_str(right)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
const SD: SelectionDesc = SelectionDesc {
|
||||||
|
left: AnchorPosition { row: 18, col: 9 },
|
||||||
|
right: AnchorPosition { row: 10, col: 1 },
|
||||||
|
};
|
||||||
|
#[test]
|
||||||
|
fn test_anchor_position() {
|
||||||
|
// Check parsing
|
||||||
|
assert_eq!(SelectionDesc::from_str("18.9,10.1").unwrap(), SD);
|
||||||
|
// Check if multiple parsed ones match
|
||||||
|
assert_eq!(
|
||||||
|
SelectionDesc::from_str("18.9,10.1").unwrap(),
|
||||||
|
SelectionDesc::from_str("18.9,10.1").unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sort() {
|
||||||
|
// Check if sorting works
|
||||||
|
assert_eq!(SD.sort(), SelectionDesc::from_str("10.1,18.9").unwrap());
|
||||||
|
assert_eq!(SD.sort(), SD.sort().sort());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_contains() {
|
||||||
|
assert!(SD.contains(&SD));
|
||||||
|
assert!(SD.contains(&SelectionDesc::from_str("17.9,10.1").unwrap()));
|
||||||
|
assert!(SD.contains(&SelectionDesc::from_str("18.8,10.1").unwrap()));
|
||||||
|
assert!(SD.contains(&SelectionDesc::from_str("18.9,11.1").unwrap()));
|
||||||
|
assert!(SD.contains(&SelectionDesc::from_str("18.9,10.2").unwrap()));
|
||||||
|
assert!(SD.contains(&SelectionDesc::from_str("10.1,17.9").unwrap()));
|
||||||
|
assert!(SD.contains(&SelectionDesc::from_str("10.1,18.8").unwrap()));
|
||||||
|
assert!(SD.contains(&SelectionDesc::from_str("11.1,18.9").unwrap()));
|
||||||
|
assert!(SD.contains(&SelectionDesc::from_str("10.2,18.9").unwrap()));
|
||||||
|
assert!(!SD.contains(&SelectionDesc::from_str("19.9,10.1").unwrap()));
|
||||||
|
assert!(!SD.contains(&SelectionDesc::from_str("18.10,10.1").unwrap()));
|
||||||
|
assert!(!SD.contains(&SelectionDesc::from_str("18.9,9.1").unwrap()));
|
||||||
|
assert!(!SD.contains(&SelectionDesc::from_str("18.9,10.0").unwrap()));
|
||||||
|
assert!(!SD.contains(&SelectionDesc::from_str("10.1,19.9").unwrap()));
|
||||||
|
assert!(!SD.contains(&SelectionDesc::from_str("10.1,18.10").unwrap()));
|
||||||
|
assert!(!SD.contains(&SelectionDesc::from_str("9.1,18.9").unwrap()));
|
||||||
|
assert!(!SD.contains(&SelectionDesc::from_str("10.0,18.9").unwrap()));
|
||||||
|
}
|
||||||
|
}
|
@ -48,6 +48,13 @@ enum Commands {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
if get_var("kak_command_fifo")
|
||||||
|
.and(get_var("kak_response_fifo"))
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
panic!("Environment variable kak_command_fifo and kak_response_fifo must be set");
|
||||||
|
}
|
||||||
|
|
||||||
let msg = match run() {
|
let msg = match run() {
|
||||||
Ok(msg) => msg,
|
Ok(msg) => msg,
|
||||||
Err(msg) => {
|
Err(msg) => {
|
||||||
|
15
src/uniq.rs
15
src/uniq.rs
@ -1,5 +1,6 @@
|
|||||||
use crate::{
|
use kakplugin::{
|
||||||
get_selections_desc, set_selections, set_selections_desc, KakMessage, SelectionWithDesc,
|
get_selections_desc, get_selections_with_desc, set_selections, set_selections_desc, KakError,
|
||||||
|
SelectionWithDesc,
|
||||||
};
|
};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use std::{
|
use std::{
|
||||||
@ -16,8 +17,8 @@ pub struct Options {
|
|||||||
#[clap(short = 'S', long)]
|
#[clap(short = 'S', long)]
|
||||||
no_skip_whitespace: bool,
|
no_skip_whitespace: bool,
|
||||||
}
|
}
|
||||||
pub fn uniq(options: &Options) -> Result<KakMessage, KakMessage> {
|
pub fn uniq(options: &Options) -> Result<String, KakError> {
|
||||||
let mut selections = crate::get_selections_with_desc()?;
|
let mut selections = get_selections_with_desc()?;
|
||||||
// Sort selections so the first element is the unique one, not an arbitrary one based on primary selection
|
// Sort selections so the first element is the unique one, not an arbitrary one based on primary selection
|
||||||
selections.sort_by_key(|s| s.desc.sort());
|
selections.sort_by_key(|s| s.desc.sort());
|
||||||
|
|
||||||
@ -89,8 +90,8 @@ pub fn uniq(options: &Options) -> Result<KakMessage, KakMessage> {
|
|||||||
let old_count = new_selections.len();
|
let old_count = new_selections.len();
|
||||||
let new_count = new_selections.iter().flatten().count();
|
let new_count = new_selections.iter().flatten().count();
|
||||||
|
|
||||||
Ok(KakMessage(
|
Ok(format!(
|
||||||
format!("{} unique selections out of {}", new_count, old_count),
|
"{} unique selections out of {}",
|
||||||
None,
|
new_count, old_count
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user