diff --git a/Cargo.lock b/Cargo.lock index 2a33d48..d5f185c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -289,6 +289,16 @@ version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +[[package]] +name = "rt-format" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93323b19fb32e6df2d1d3d9e86ae0f3ad72ec0d4c23f6ef7d3fcb898d888b0b0" +dependencies = [ + "lazy_static", + "regex", +] + [[package]] name = "ryu" version = "1.0.9" @@ -363,6 +373,7 @@ dependencies = [ "lazy_static", "rand", "regex", + "rt-format", "serde", "serde_json", "swayipc", diff --git a/Cargo.toml b/Cargo.toml index 145fd52..31768cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,3 +19,4 @@ directories = "4.0" regex = "1.5.4" lazy_static = "1.4.0" rand = "0.8.4" +rt-format = "0.2.0" diff --git a/NEWS.md b/NEWS.md index c5f1e6b..607ad93 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,11 +1,13 @@ swayr v0.13.0 ============= -- The placeholders `{app_name}`, `{name}`, `{output_name}`, and - `{workspace_name}` allow to specify the maximum string length using format - `{:}` (e.g. `{app_name:10}`). If the string is longer than the - specified length, it will be truncated and an ellipsis ("…") will be inserted - at the end. +- 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 `{:}`. 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. swayr v0.12.0 ============= diff --git a/README.md b/README.md index 6aadb00..086b3f4 100644 --- a/README.md +++ b/README.md @@ -324,11 +324,13 @@ right now. * `fallback_icon` is a path to some PNG/SVG icon which will be used as `{app_icon}` if no application-specific icon can be determined. -The placeholders `{app_name}`, `{name}`, `{output_name}`, and -`{workspace_name}` allow to specify the maximum string length using format -`{:}` (e.g. `{app_name:10}`). If the string is longer than the -specified length, it will be truncated and an ellipsis ("…") will be inserted -at the end. +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 `{:}`. 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. 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/bin/swayr.rs b/src/bin/swayr.rs index 1cee6ed..9b4b71d 100644 --- a/src/bin/swayr.rs +++ b/src/bin/swayr.rs @@ -1,4 +1,4 @@ -// Copyright (C) 2021 Tassilo Horn +// Copyright (C) 2021-2022 Tassilo Horn // // This program is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by the Free diff --git a/src/bin/swayrd.rs b/src/bin/swayrd.rs index 98fb651..53c1ef1 100644 --- a/src/bin/swayrd.rs +++ b/src/bin/swayrd.rs @@ -1,4 +1,4 @@ -// Copyright (C) 2021 Tassilo Horn +// Copyright (C) 2021-2022 Tassilo Horn // // This program is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by the Free diff --git a/src/client.rs b/src/client.rs index a3a830f..a92318a 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,4 +1,4 @@ -// Copyright (C) 2021 Tassilo Horn +// Copyright (C) 2021-2022 Tassilo Horn // // This program is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by the Free diff --git a/src/cmds.rs b/src/cmds.rs index 9144250..950c180 100644 --- a/src/cmds.rs +++ b/src/cmds.rs @@ -1,4 +1,4 @@ -// Copyright (C) 2021 Tassilo Horn +// Copyright (C) 2021-2022 Tassilo Horn // // This program is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by the Free diff --git a/src/config.rs b/src/config.rs index a176642..14c6e16 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,4 +1,4 @@ -// Copyright (C) 2021 Tassilo Horn +// Copyright (C) 2021-2022 Tassilo Horn // // This program is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by the Free diff --git a/src/demon.rs b/src/demon.rs index 4b1b769..25e3158 100644 --- a/src/demon.rs +++ b/src/demon.rs @@ -1,4 +1,4 @@ -// Copyright (C) 2021 Tassilo Horn +// Copyright (C) 2021-2022 Tassilo Horn // // This program is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by the Free diff --git a/src/layout.rs b/src/layout.rs index ac4f099..f00f512 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -1,4 +1,4 @@ -// Copyright (C) 2021 Tassilo Horn +// Copyright (C) 2021-2022 Tassilo Horn // // This program is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by the Free diff --git a/src/lib.rs b/src/lib.rs index e269fb3..ca73b19 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright (C) 2021 Tassilo Horn +// Copyright (C) 2021-2022 Tassilo Horn // // This program is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by the Free @@ -28,5 +28,6 @@ pub mod cmds; pub mod config; pub mod demon; pub mod layout; +pub mod rtfmt; pub mod tree; pub mod util; diff --git a/src/rtfmt.rs b/src/rtfmt.rs new file mode 100644 index 0000000..26966a7 --- /dev/null +++ b/src/rtfmt.rs @@ -0,0 +1,83 @@ +// Copyright (C) 2022 Tassilo Horn +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . + +//! Provides runtime formatting of strings since our format strings are read +//! from the swayr config, not specified as literals, e.g., `{name:{:.30}}` in +//! `format.window_format`. + +use rt_format::{ + Format, FormatArgument, NoNamedArguments, ParsedFormat, Specifier, +}; +use std::fmt; + +enum FmtArg<'a> { + Str(&'a str), +} + +impl<'a> FormatArgument for FmtArg<'a> { + fn supports_format(&self, spec: &Specifier) -> bool { + spec.format == Format::Display + } + + fn fmt_display(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Str(val) => fmt::Display::fmt(&val, f), + } + } + + fn fmt_debug(&self, _f: &mut fmt::Formatter) -> fmt::Result { + Err(fmt::Error) + } + + fn fmt_octal(&self, _f: &mut fmt::Formatter) -> fmt::Result { + Err(fmt::Error) + } + + fn fmt_lower_hex(&self, _f: &mut fmt::Formatter) -> fmt::Result { + Err(fmt::Error) + } + + fn fmt_upper_hex(&self, _f: &mut fmt::Formatter) -> fmt::Result { + Err(fmt::Error) + } + + fn fmt_binary(&self, _f: &mut fmt::Formatter) -> fmt::Result { + Err(fmt::Error) + } + + fn fmt_lower_exp(&self, _f: &mut fmt::Formatter) -> fmt::Result { + Err(fmt::Error) + } + + fn fmt_upper_exp(&self, _f: &mut fmt::Formatter) -> fmt::Result { + Err(fmt::Error) + } +} + +impl<'a> std::convert::TryInto for &FmtArg<'a> { + type Error = (); + fn try_into(self) -> Result { + Err(()) + } +} + +pub fn format(fmt: &str, arg: &str) -> String { + let args = &[FmtArg::Str(arg)]; + if let Ok(pf) = ParsedFormat::parse(fmt, args, &NoNamedArguments) { + format!("{}", pf) + } else { + format!("Invalid format string: {}", fmt) + } +} diff --git a/src/tree.rs b/src/tree.rs index ea02070..b145552 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -1,4 +1,4 @@ -// Copyright (C) 2021 Tassilo Horn +// Copyright (C) 2021-2022 Tassilo Horn // // This program is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by the Free @@ -16,6 +16,7 @@ //! Convenience data structures built from the IPC structs. use crate::config; +use crate::rtfmt; use crate::util; use crate::util::DisplayFormat; use lazy_static::lazy_static; @@ -427,20 +428,18 @@ pub fn get_tree<'a>( lazy_static! { static ref APP_NAME_AND_VERSION_RX: regex::Regex = regex::Regex::new("(.+)(-[0-9.]+)").unwrap(); -} - -lazy_static! { static ref PLACEHOLDER_RX: regex::Regex = - regex::Regex::new(r"\{(?P[^}:]+)(?::(?P\d+))?\}").unwrap(); + regex::Regex::new(r"\{(?P[^}:]+)(?::(?P\{[^}]*\}))?\}") + .unwrap(); } -fn maybe_html_escape(do_it: bool, text: &str) -> String { +fn maybe_html_escape(do_it: bool, text: String) -> String { if do_it { text.replace("<", "<") .replace(">", ">") .replace("&", "&") } else { - text.to_string() + text } } @@ -477,14 +476,6 @@ impl DisplayFormat for DisplayNode<'_> { }; let fmt = fmt .replace("{indent}", &indent.repeat(self.get_indent_level())) - .replace("{id}", format!("{}", self.node.id).as_str()) - .replace( - "{marks}", - &maybe_html_escape( - html_escape, - &format_marks(&self.node.marks), - ), - ) .replace( "{urgency_start}", if self.node.urgent { @@ -522,32 +513,24 @@ impl DisplayFormat for DisplayNode<'_> { PLACEHOLDER_RX .replace_all(&fmt, |caps: ®ex::Captures| { let value = match &caps["name"] { - "app_name" => self.node.get_app_name(), - "name" | "title" => self.node.get_name(), + "id" => self.node.id.to_string(), + "app_name" => self.node.get_app_name().to_string(), + "name" | "title" => self.node.get_name().to_string(), "output_name" => self .tree .get_parent_node_of_type(self.node.id, Type::Output) - .map_or("", |w| w.get_name()), + .map_or("", |w| w.get_name()) + .to_string(), "workspace_name" => self .tree .get_parent_node_of_type(self.node.id, Type::Workspace) - .map_or("", |w| w.get_name()), - _ => &caps[0], + .map_or("", |w| w.get_name()) + .to_string(), + "marks" => format_marks(&self.node.marks), + _ => caps[0].to_string(), }; - let width = caps - .name("width") - .map_or("0", |m| m.as_str()) - .parse::() - .unwrap(); - - if width > 0 && value.len() > width { - maybe_html_escape( - html_escape, - &format!("{}…", &value[..width - 1]), - ) - } else { - maybe_html_escape(html_escape, value) - } + let fmt_str = caps.name("fmtstr").map_or("{}", |m| m.as_str()); + maybe_html_escape(html_escape, rtfmt::format(fmt_str, &value)) }) .into() } diff --git a/src/util.rs b/src/util.rs index a7f5f54..6c7f50c 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,4 +1,4 @@ -// Copyright (C) 2021 Tassilo Horn +// Copyright (C) 2021-2022 Tassilo Horn // // This program is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by the Free