Allow placeholders to specify their own format string

timeout_old
Tassilo Horn 3 years ago
parent 784df6582e
commit 053becd176
  1. 11
      Cargo.lock
  2. 1
      Cargo.toml
  3. 12
      NEWS.md
  4. 12
      README.md
  5. 2
      src/bin/swayr.rs
  6. 2
      src/bin/swayrd.rs
  7. 2
      src/client.rs
  8. 2
      src/cmds.rs
  9. 2
      src/config.rs
  10. 2
      src/demon.rs
  11. 2
      src/layout.rs
  12. 3
      src/lib.rs
  13. 83
      src/rtfmt.rs
  14. 51
      src/tree.rs
  15. 2
      src/util.rs

11
Cargo.lock generated

@ -289,6 +289,16 @@ version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 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]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.9" version = "1.0.9"
@ -363,6 +373,7 @@ dependencies = [
"lazy_static", "lazy_static",
"rand", "rand",
"regex", "regex",
"rt-format",
"serde", "serde",
"serde_json", "serde_json",
"swayipc", "swayipc",

@ -19,3 +19,4 @@ directories = "4.0"
regex = "1.5.4" regex = "1.5.4"
lazy_static = "1.4.0" lazy_static = "1.4.0"
rand = "0.8.4" rand = "0.8.4"
rt-format = "0.2.0"

@ -1,11 +1,13 @@
swayr v0.13.0 swayr v0.13.0
============= =============
- The placeholders `{app_name}`, `{name}`, `{output_name}`, and - The placeholders `{id}`, `{app_name}`, `{name}`/`{title}`, `{output_name}`,
`{workspace_name}` allow to specify the maximum string length using format `{workspace_name}`, and `{marks}` may optionally provide a format string as
`{<name>:<len>}` (e.g. `{app_name:10}`). If the string is longer than the specified by [Rust's std::fmt](https://doc.rust-lang.org/std/fmt/). The
specified length, it will be truncated and an ellipsis ("…") will be inserted syntax is `{<placeholder>:<fmt_str>}`. For example, `{app_name:{:>10.10}}`
at the end. 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 swayr v0.12.0
============= =============

@ -324,11 +324,13 @@ right now.
* `fallback_icon` is a path to some PNG/SVG icon which will be used as * `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. `{app_icon}` if no application-specific icon can be determined.
The placeholders `{app_name}`, `{name}`, `{output_name}`, and The placeholders `{id}`, `{app_name}`, `{name}`/`{title}`, `{output_name}`,
`{workspace_name}` allow to specify the maximum string length using format `{workspace_name}`, and `{marks}` may optionally provide a format string as
`{<name>:<len>}` (e.g. `{app_name:10}`). If the string is longer than the specified by [Rust's std::fmt](https://doc.rust-lang.org/std/fmt/). The syntax
specified length, it will be truncated and an ellipsis ("…") will be inserted is `{<placeholder>:<fmt_str>}`. For example, `{app_name:{:>10.10}}` would mean
at the end. 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) It is crucial that during selection (using wofi or some other menu program)
each window has a different display string. Therefore, it is highly each window has a different display string. Therefore, it is highly

@ -1,4 +1,4 @@
// Copyright (C) 2021 Tassilo Horn <tsdh@gnu.org> // Copyright (C) 2021-2022 Tassilo Horn <tsdh@gnu.org>
// //
// This program is free software: you can redistribute it and/or modify it // 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 // under the terms of the GNU General Public License as published by the Free

@ -1,4 +1,4 @@
// Copyright (C) 2021 Tassilo Horn <tsdh@gnu.org> // Copyright (C) 2021-2022 Tassilo Horn <tsdh@gnu.org>
// //
// This program is free software: you can redistribute it and/or modify it // 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 // under the terms of the GNU General Public License as published by the Free

@ -1,4 +1,4 @@
// Copyright (C) 2021 Tassilo Horn <tsdh@gnu.org> // Copyright (C) 2021-2022 Tassilo Horn <tsdh@gnu.org>
// //
// This program is free software: you can redistribute it and/or modify it // 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 // under the terms of the GNU General Public License as published by the Free

@ -1,4 +1,4 @@
// Copyright (C) 2021 Tassilo Horn <tsdh@gnu.org> // Copyright (C) 2021-2022 Tassilo Horn <tsdh@gnu.org>
// //
// This program is free software: you can redistribute it and/or modify it // 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 // under the terms of the GNU General Public License as published by the Free

@ -1,4 +1,4 @@
// Copyright (C) 2021 Tassilo Horn <tsdh@gnu.org> // Copyright (C) 2021-2022 Tassilo Horn <tsdh@gnu.org>
// //
// This program is free software: you can redistribute it and/or modify it // 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 // under the terms of the GNU General Public License as published by the Free

@ -1,4 +1,4 @@
// Copyright (C) 2021 Tassilo Horn <tsdh@gnu.org> // Copyright (C) 2021-2022 Tassilo Horn <tsdh@gnu.org>
// //
// This program is free software: you can redistribute it and/or modify it // 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 // under the terms of the GNU General Public License as published by the Free

@ -1,4 +1,4 @@
// Copyright (C) 2021 Tassilo Horn <tsdh@gnu.org> // Copyright (C) 2021-2022 Tassilo Horn <tsdh@gnu.org>
// //
// This program is free software: you can redistribute it and/or modify it // 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 // under the terms of the GNU General Public License as published by the Free

@ -1,4 +1,4 @@
// Copyright (C) 2021 Tassilo Horn <tsdh@gnu.org> // Copyright (C) 2021-2022 Tassilo Horn <tsdh@gnu.org>
// //
// This program is free software: you can redistribute it and/or modify it // 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 // 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 config;
pub mod demon; pub mod demon;
pub mod layout; pub mod layout;
pub mod rtfmt;
pub mod tree; pub mod tree;
pub mod util; pub mod util;

@ -0,0 +1,83 @@
// Copyright (C) 2022 Tassilo Horn <tsdh@gnu.org>
//
// 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 <https://www.gnu.org/licenses/>.
//! 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<usize> for &FmtArg<'a> {
type Error = ();
fn try_into(self) -> Result<usize, Self::Error> {
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)
}
}

@ -1,4 +1,4 @@
// Copyright (C) 2021 Tassilo Horn <tsdh@gnu.org> // Copyright (C) 2021-2022 Tassilo Horn <tsdh@gnu.org>
// //
// This program is free software: you can redistribute it and/or modify it // 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 // 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. //! Convenience data structures built from the IPC structs.
use crate::config; use crate::config;
use crate::rtfmt;
use crate::util; use crate::util;
use crate::util::DisplayFormat; use crate::util::DisplayFormat;
use lazy_static::lazy_static; use lazy_static::lazy_static;
@ -427,20 +428,18 @@ pub fn get_tree<'a>(
lazy_static! { lazy_static! {
static ref APP_NAME_AND_VERSION_RX: regex::Regex = static ref APP_NAME_AND_VERSION_RX: regex::Regex =
regex::Regex::new("(.+)(-[0-9.]+)").unwrap(); regex::Regex::new("(.+)(-[0-9.]+)").unwrap();
}
lazy_static! {
static ref PLACEHOLDER_RX: regex::Regex = static ref PLACEHOLDER_RX: regex::Regex =
regex::Regex::new(r"\{(?P<name>[^}:]+)(?::(?P<width>\d+))?\}").unwrap(); regex::Regex::new(r"\{(?P<name>[^}:]+)(?::(?P<fmtstr>\{[^}]*\}))?\}")
.unwrap();
} }
fn maybe_html_escape(do_it: bool, text: &str) -> String { fn maybe_html_escape(do_it: bool, text: String) -> String {
if do_it { if do_it {
text.replace("<", "&lt;") text.replace("<", "&lt;")
.replace(">", "&gt;") .replace(">", "&gt;")
.replace("&", "&amp;") .replace("&", "&amp;")
} else { } else {
text.to_string() text
} }
} }
@ -477,14 +476,6 @@ impl DisplayFormat for DisplayNode<'_> {
}; };
let fmt = fmt let fmt = fmt
.replace("{indent}", &indent.repeat(self.get_indent_level())) .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( .replace(
"{urgency_start}", "{urgency_start}",
if self.node.urgent { if self.node.urgent {
@ -522,32 +513,24 @@ impl DisplayFormat for DisplayNode<'_> {
PLACEHOLDER_RX PLACEHOLDER_RX
.replace_all(&fmt, |caps: &regex::Captures| { .replace_all(&fmt, |caps: &regex::Captures| {
let value = match &caps["name"] { let value = match &caps["name"] {
"app_name" => self.node.get_app_name(), "id" => self.node.id.to_string(),
"name" | "title" => self.node.get_name(), "app_name" => self.node.get_app_name().to_string(),
"name" | "title" => self.node.get_name().to_string(),
"output_name" => self "output_name" => self
.tree .tree
.get_parent_node_of_type(self.node.id, Type::Output) .get_parent_node_of_type(self.node.id, Type::Output)
.map_or("<no_output>", |w| w.get_name()), .map_or("<no_output>", |w| w.get_name())
.to_string(),
"workspace_name" => self "workspace_name" => self
.tree .tree
.get_parent_node_of_type(self.node.id, Type::Workspace) .get_parent_node_of_type(self.node.id, Type::Workspace)
.map_or("<no_workspace>", |w| w.get_name()), .map_or("<no_workspace>", |w| w.get_name())
_ => &caps[0], .to_string(),
"marks" => format_marks(&self.node.marks),
_ => caps[0].to_string(),
}; };
let width = caps let fmt_str = caps.name("fmtstr").map_or("{}", |m| m.as_str());
.name("width") maybe_html_escape(html_escape, rtfmt::format(fmt_str, &value))
.map_or("0", |m| m.as_str())
.parse::<usize>()
.unwrap();
if width > 0 && value.len() > width {
maybe_html_escape(
html_escape,
&format!("{}…", &value[..width - 1]),
)
} else {
maybe_html_escape(html_escape, value)
}
}) })
.into() .into()
} }

@ -1,4 +1,4 @@
// Copyright (C) 2021 Tassilo Horn <tsdh@gnu.org> // Copyright (C) 2021-2022 Tassilo Horn <tsdh@gnu.org>
// //
// This program is free software: you can redistribute it and/or modify it // 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 // under the terms of the GNU General Public License as published by the Free

Loading…
Cancel
Save