From 7a57f6e541f7c090155118d92103264d253f9f03 Mon Sep 17 00:00:00 2001 From: Jakub Jirutka Date: Sun, 30 Jan 2022 03:31:19 +0100 Subject: [PATCH] Allow to use custom "ellipsis" string --- NEWS.md | 17 +++++++++-------- README.md | 8 ++++---- src/rtfmt.rs | 31 ++++++++++++++++++++----------- src/tree.rs | 18 ++++++++++++------ 4 files changed, 45 insertions(+), 29 deletions(-) diff --git a/NEWS.md b/NEWS.md index 19d377f..0efe52f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,14 +4,15 @@ swayr v0.13.0 - The placeholders `{id}`, `{app_name}`, `{name}`/`{title}`, `{output_name}`, `{workspace_name}`, and `{marks}` may optionally provide a format string as specified by [Rust's std::fmt](https://doc.rust-lang.org/std/fmt/). The - syntax is `{:}` or `{:…}`. For - example, `{app_name:{:>10.10}}` would mean that the application name is - printed with exactly 10 characters. If it's shorter, it will be - right-aligned (the `>`) and padded with spaces, if it's longer, it'll be cut - after the 10th character. Another example, `{app_name:{:.10}…}` would mean - that the application name is truncated at 10 characters. If it's shorter, it - will be printed as-is (no padding), if it's longer, it'll be cut after the - 9th character and the ellipsis (…) character will be added after it. + syntax is `{:}`. For example, + `{app_name:{:>10.10}}` would mean that the application name is printed with + exactly 10 characters. If it's shorter, it will be right-aligned (the `>`) + and padded with spaces, if it's longer, it'll be cut after the 10th + character. Another example, `{app_name:{:.10}...}` would mean that the + application name is truncated at 10 characters. If it's shorter, it will be + printed as-is (no padding), if it's longer, it'll be cut after the 10th + character and the last 3 characters of that substring will be replaced with + `...` (``). swayr v0.12.0 ============= diff --git a/README.md b/README.md index 323d9b1..522a664 100644 --- a/README.md +++ b/README.md @@ -327,14 +327,14 @@ right now. The placeholders `{id}`, `{app_name}`, `{name}`/`{title}`, `{output_name}`, `{workspace_name}`, and `{marks}` may optionally provide a format string as specified by [Rust's std::fmt](https://doc.rust-lang.org/std/fmt/). The syntax -is `{:}` or `{:…}`. For example, +is `{:}`. For example, `{app_name:{:>10.10}}` would mean that the application name is printed with exactly 10 characters. If it's shorter, it will be right-aligned (the `>`) and padded with spaces, if it's longer, it'll be cut after the 10th character. -Another example, `{app_name:{:.10}…}` would mean that the application name is +Another example, `{app_name:{:.10}...}` would mean that the application name is truncated at 10 characters. If it's shorter, it will be printed as-is (no -padding), if it's longer, it'll be cut after the 9th character and the ellipsis -(…) character will be added after it. +padding), if it's longer, it'll be cut after the 10th character and the last +3 characters of that substring will be replaced with `...` (``). It is crucial that during selection (using wofi or some other menu program) each window has a different display string. Therefore, it is highly diff --git a/src/rtfmt.rs b/src/rtfmt.rs index e8ac462..2437423 100644 --- a/src/rtfmt.rs +++ b/src/rtfmt.rs @@ -73,15 +73,15 @@ impl<'a> std::convert::TryInto for &FmtArg<'a> { } } -pub fn format(fmt: &str, arg: &str, ellipsis: bool) -> String { +pub fn format(fmt: &str, arg: &str, clipped_str: &str) -> String { let args = &[FmtArg::Str(arg)]; if let Ok(pf) = ParsedFormat::parse(fmt, args, &NoNamedArguments) { let mut s = format!("{}", pf); - if ellipsis && !s.contains(arg) { - s.pop(); - s.push('…'); + if !clipped_str.is_empty() && !s.contains(arg) { + remove_last_n_chars(&mut s, clipped_str.chars().count()); + s.push_str(clipped_str); } s } else { @@ -89,13 +89,22 @@ pub fn format(fmt: &str, arg: &str, ellipsis: bool) -> String { } } +fn remove_last_n_chars(s: &mut String, n: usize) -> () { + match s.char_indices().nth_back(n) { + Some((pos, ch)) => s.truncate(pos + ch.len_utf8()), + None => s.clear(), + } +} + #[test] fn test_format() { - assert_eq!(format("{:.10}", "sway", false), "sway"); - assert_eq!(format("{:.10}", "sway", true), "sway"); - assert_eq!(format("{:.4}", "𝔰𝔴𝔞𝔶", true), "𝔰𝔴𝔞𝔶"); - - assert_eq!(format("{:.3}", "sway", false), "swa"); - assert_eq!(format("{:.3}", "sway", true), "sw…"); - assert_eq!(format("{:.3}", "𝔰𝔴𝔞𝔶", true), "𝔰𝔴…"); + assert_eq!(format("{:.10}", "sway", ""), "sway"); + assert_eq!(format("{:.10}", "sway", "…"), "sway"); + assert_eq!(format("{:.4}", "𝔰𝔴𝔞𝔶", "……"), "𝔰𝔴𝔞𝔶"); + + assert_eq!(format("{:.3}", "sway", ""), "swa"); + assert_eq!(format("{:.3}", "sway", "…"), "sw…"); + assert_eq!(format("{:.5}", "𝔰𝔴𝔞𝔶 𝔴𝔦𝔫𝔡𝔬𝔴", "…?"), "𝔰𝔴𝔞…?"); + assert_eq!(format("{:.5}", "sway window", "..."), "sw..."); + assert_eq!(format("{:.2}", "sway", "..."), "..."); } diff --git a/src/tree.rs b/src/tree.rs index fde5d8a..eb5e64f 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -429,7 +429,7 @@ lazy_static! { static ref APP_NAME_AND_VERSION_RX: regex::Regex = regex::Regex::new("(.+)(-[0-9.]+)").unwrap(); static ref PLACEHOLDER_RX: regex::Regex = regex::Regex::new( - r"\{(?P[^}:]+)(?::(?P\{[^}]*\})(?P…)?)?\}" + r"\{(?P[^}:]+)(?::(?P\{[^}]*\})(?P[^}]*))?\}" ) .unwrap(); } @@ -531,11 +531,12 @@ impl DisplayFormat for DisplayNode<'_> { _ => caps[0].to_string(), }; let fmt_str = caps.name("fmtstr").map_or("{}", |m| m.as_str()); - let ellipsis = caps.name("ellipsis").is_some(); + let clipped_str = + caps.name("clipstr").map_or("", |m| m.as_str()); maybe_html_escape( html_escape, - rtfmt::format(fmt_str, &value, ellipsis), + rtfmt::format(fmt_str, &value, &clipped_str), ) }) .into() @@ -573,15 +574,20 @@ fn test_placeholder_rx() { let caps = PLACEHOLDER_RX.captures("Hello, {place}!").unwrap(); assert_eq!(caps.name("name").unwrap().as_str(), "place"); assert_eq!(caps.name("fmtstr"), None); - assert_eq!(caps.name("ellipsis"), None); + assert_eq!(caps.name("clipstr"), None); let caps = PLACEHOLDER_RX.captures("Hi, {place:{:>10.10}}!").unwrap(); assert_eq!(caps.name("name").unwrap().as_str(), "place"); assert_eq!(caps.name("fmtstr").unwrap().as_str(), "{:>10.10}"); - assert_eq!(caps.name("ellipsis"), None); + assert_eq!(caps.name("clipstr").unwrap().as_str(), ""); let caps = PLACEHOLDER_RX.captures("Hello, {place:{:.5}…}!").unwrap(); assert_eq!(caps.name("name").unwrap().as_str(), "place"); assert_eq!(caps.name("fmtstr").unwrap().as_str(), "{:.5}"); - assert_eq!(caps.name("ellipsis").unwrap().as_str(), "…"); + assert_eq!(caps.name("clipstr").unwrap().as_str(), "…"); + + let caps = PLACEHOLDER_RX.captures("Hello, {place:{:.5}...}!").unwrap(); + assert_eq!(caps.name("name").unwrap().as_str(), "place"); + assert_eq!(caps.name("fmtstr").unwrap().as_str(), "{:.5}"); + assert_eq!(caps.name("clipstr").unwrap().as_str(), "..."); }