diff --git a/NEWS.md b/NEWS.md index f6e8651..c5f1e6b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,12 @@ +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. + swayr v0.12.0 ============= diff --git a/README.md b/README.md index 953c133..b2d02f7 100644 --- a/README.md +++ b/README.md @@ -324,6 +324,11 @@ 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. + It is crucial that during selection (using wofi or some other menu program) each window has a different display string. Therefore, it is highly recommended to include the `{id}` placeholder at least in `container_format` diff --git a/src/tree.rs b/src/tree.rs index 3b293f8..418ba0a 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -429,6 +429,11 @@ lazy_static! { regex::Regex::new("(.+)(-[0-9.]+)").unwrap(); } +lazy_static! { + static ref PLACEHOLDER_RX: regex::Regex = + regex::Regex::new(r"\{(?P[^}:]+)(?::(?P\d+))?\}").unwrap(); +} + fn maybe_html_escape(do_it: bool, text: &str) -> String { if do_it { text.replace("<", "<") @@ -470,8 +475,8 @@ impl DisplayFormat for DisplayNode<'_> { Type::Container => cfg.get_format_container_format(), Type::Window => cfg.get_format_window_format(), }; - fmt.replace("{indent}", &indent.repeat(self.get_indent_level())) - .replace("{layout}", format!("{:?}", self.node.layout).as_str()) + let fmt = fmt + .replace("{indent}", &indent.repeat(self.get_indent_level())) .replace("{id}", format!("{}", self.node.id).as_str()) .replace( "{marks}", @@ -496,28 +501,6 @@ impl DisplayFormat for DisplayNode<'_> { "" }, ) - .replace( - "{app_name}", - &maybe_html_escape(html_escape, self.node.get_app_name()), - ) - .replace( - "{output_name}", - &maybe_html_escape( - html_escape, - self.tree - .get_parent_node_of_type(self.node.id, Type::Output) - .map_or("", |w| w.get_name()), - ), - ) - .replace( - "{workspace_name}", - &maybe_html_escape( - html_escape, - self.tree - .get_parent_node_of_type(self.node.id, Type::Workspace) - .map_or("", |w| w.get_name()), - ), - ) .replace( "{app_icon}", util::get_icon(self.node.get_app_name(), &icon_dirs) @@ -534,15 +517,34 @@ impl DisplayFormat for DisplayNode<'_> { .map(|i| i.to_string_lossy().into_owned()) .unwrap_or_else(String::new) .as_str(), - ) - .replace( - "{title}", - &maybe_html_escape(html_escape, self.node.get_name()), - ) - .replace( - "{name}", - &maybe_html_escape(html_escape, self.node.get_name()), - ) + ); + 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(), + "output_name" => { + self.tree + .get_parent_node_of_type(self.node.id, Type::Output) + .map_or("", |w| w.get_name()) + }, + "workspace_name" => { + self.tree + .get_parent_node_of_type(self.node.id, Type::Workspace) + .map_or("", |w| w.get_name()) + }, + _ => &caps[0], + }; + 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) + } + }).into() } fn get_indent_level(&self) -> usize {