From 061c3589ac2e80de607bc169f72364cd152dd937 Mon Sep 17 00:00:00 2001 From: Tassilo Horn Date: Fri, 1 Apr 2022 22:06:19 +0200 Subject: [PATCH] Initial swayrbar impl --- Cargo.lock | 192 ++++++++++++++++++++++++++++++++++++++ Cargo.toml | 7 +- src/bar.rs | 58 ++++++++++++ src/bar/module.rs | 31 ++++++ src/bar/module/date.rs | 62 ++++++++++++ src/bar/module/sysinfo.rs | 71 ++++++++++++++ src/bar/module/window.rs | 105 +++++++++++++++++++++ src/bin/swayrbar.rs | 22 +++++ src/fmt_replace.rs | 71 ++++++++++++++ src/lib.rs | 3 + src/tree.rs | 62 ++++-------- 11 files changed, 636 insertions(+), 48 deletions(-) create mode 100644 src/bar.rs create mode 100644 src/bar/module.rs create mode 100644 src/bar/module/date.rs create mode 100644 src/bar/module/sysinfo.rs create mode 100644 src/bar/module/window.rs create mode 100644 src/bin/swayrbar.rs create mode 100644 src/fmt_replace.rs diff --git a/Cargo.lock b/Cargo.lock index 6919098..bc79fb7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -40,6 +40,19 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time", + "winapi", +] + [[package]] name = "clap" version = "3.1.8" @@ -70,6 +83,57 @@ dependencies = [ "syn", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "crossbeam-channel" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "lazy_static", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" +dependencies = [ + "cfg-if", + "lazy_static", +] + [[package]] name = "directories" version = "4.0.1" @@ -90,6 +154,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + [[package]] name = "env_logger" version = "0.9.0" @@ -183,6 +253,59 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "ntapi" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" +dependencies = [ + "winapi", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" + [[package]] name = "os_str_bytes" version = "6.0.0" @@ -270,6 +393,31 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rayon" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" +dependencies = [ + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "lazy_static", + "num_cpus", +] + [[package]] name = "redox_syscall" version = "0.2.13" @@ -323,6 +471,12 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + [[package]] name = "serde" version = "1.0.136" @@ -360,6 +514,16 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "swaybar-types" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2c0b435952b89d872f882cf7ae0756303ef68d310bfa44b9c8012fda88ae143" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "swayipc" version = "3.0.0" @@ -386,6 +550,7 @@ dependencies = [ name = "swayr" version = "0.16.1" dependencies = [ + "chrono", "clap", "directories", "env_logger", @@ -396,7 +561,9 @@ dependencies = [ "rt-format", "serde", "serde_json", + "swaybar-types", "swayipc", + "sysinfo", "toml", ] @@ -411,6 +578,21 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "sysinfo" +version = "0.23.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f3c0db8e08e06cfd352a043bd0143498fb7d42783a6e941da83b55464eb27d2" +dependencies = [ + "cfg-if", + "core-foundation-sys", + "libc", + "ntapi", + "once_cell", + "rayon", + "winapi", +] + [[package]] name = "termcolor" version = "1.1.3" @@ -446,6 +628,16 @@ dependencies = [ "syn", ] +[[package]] +name = "time" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "toml" version = "0.5.8" diff --git a/Cargo.toml b/Cargo.toml index 648ec2b..f7bd171 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,9 +22,10 @@ rand = "0.8.4" rt-format = "0.3.0" log = "0.4" env_logger = { version = "0.9.0", default-features = false, features = ["termcolor", "atty", "humantime"] } # without regex - -[profile.dev] -lto = "thin" +swaybar-types = "3.0.0" +chrono = "0.4" +sysinfo = "0.23" [profile.release] lto = "thin" +strip = "symbols" \ No newline at end of file diff --git a/src/bar.rs b/src/bar.rs new file mode 100644 index 0000000..cfdaf36 --- /dev/null +++ b/src/bar.rs @@ -0,0 +1,58 @@ +// 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 . + +//! `swayrbar` lib. + +use crate::bar::module::BarModuleFn; +use env_logger::Env; +use serde_json; +use std::thread; + +pub mod module; + +pub fn start() { + env_logger::Builder::from_env(Env::default().default_filter_or("warn")) + .init(); + + thread::spawn(handle_input); + let mods: Vec> = vec![ + crate::bar::module::window::BarModuleWindow::init(), + crate::bar::module::sysinfo::BarModuleSysInfo::init(), + crate::bar::module::date::BarModuleDate::init(), + ]; + generate_status(&mods); +} + +pub fn handle_input() { + // TODO: Read stdin and react to click events. +} + +pub fn generate_status(mods: &[Box]) { + println!("{{\"version\": 1}}"); + // status_command should output an infinite array meaning we emit an + // opening [ and never the closing bracket. + println!("["); + + loop { + let mut blocks = vec![]; + for m in mods { + blocks.push(m.build()); + } + let json = serde_json::to_string_pretty(&blocks) + .unwrap_or_else(|_| "".to_string()); + println!("{},", json); + thread::sleep(std::time::Duration::from_secs(1)); + } +} diff --git a/src/bar/module.rs b/src/bar/module.rs new file mode 100644 index 0000000..f39c7bb --- /dev/null +++ b/src/bar/module.rs @@ -0,0 +1,31 @@ +// 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 . + +use swaybar_types as s; + +pub mod date; +pub mod sysinfo; +pub mod window; + +pub trait BarModuleFn { + fn init() -> Box + where + Self: Sized; + fn name() -> String + where + Self: Sized; + fn instance(&self) -> String; + fn build(&self) -> s::Block; +} diff --git a/src/bar/module/date.rs b/src/bar/module/date.rs new file mode 100644 index 0000000..24f7f3e --- /dev/null +++ b/src/bar/module/date.rs @@ -0,0 +1,62 @@ +// 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 . + +//! The date `swayrbar` module. + +use crate::bar::module::BarModuleFn; +use swaybar_types as s; + +pub struct BarModuleDate { + pub instance: String, +} + +impl BarModuleFn for BarModuleDate { + fn init() -> Box { + Box::new(BarModuleDate { + instance: "0".to_string(), + }) + } + + fn name() -> String { + String::from("date") + } + + fn instance(&self) -> String { + self.instance.clone() + } + + fn build(&self) -> s::Block { + let d = chrono::Local::now().format("%F %X").to_string(); + s::Block { + name: Some(Self::name()), + instance: Some(self.instance.clone()), + full_text: d, + align: Some(s::Align::Right), + markup: Some(s::Markup::Pango), + short_text: None, + color: None, + background: None, + border: None, + border_top: None, + border_bottom: None, + border_left: None, + border_right: None, + min_width: None, + urgent: None, + separator: None, + separator_block_width: None, + } + } +} diff --git a/src/bar/module/sysinfo.rs b/src/bar/module/sysinfo.rs new file mode 100644 index 0000000..a5743fc --- /dev/null +++ b/src/bar/module/sysinfo.rs @@ -0,0 +1,71 @@ +// 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 . + +//! The date `swayrbar` module. + +use crate::bar::module::BarModuleFn; +use std::cell::RefCell; +use swaybar_types as s; +use sysinfo; +use sysinfo::SystemExt; + +pub struct BarModuleSysInfo { + pub instance: String, + system: RefCell, +} + +impl BarModuleFn for BarModuleSysInfo { + fn init() -> Box { + Box::new(BarModuleSysInfo { + instance: "0".to_string(), + system: RefCell::new(sysinfo::System::new_all()), + }) + } + + fn name() -> String { + String::from("sysinfo") + } + + fn instance(&self) -> String { + self.instance.clone() + } + + fn build(&self) -> s::Block { + let x = String::from("{cpu_usage_avg}"); + self.system.borrow_mut().refresh_specifics( + sysinfo::RefreshKind::new().with_cpu().with_memory(), + ); + + s::Block { + name: Some(Self::name()), + instance: Some(self.instance.clone()), + full_text: x, + align: Some(s::Align::Right), + markup: Some(s::Markup::Pango), + short_text: None, + color: None, + background: None, + border: None, + border_top: None, + border_bottom: None, + border_left: None, + border_right: None, + min_width: None, + urgent: None, + separator: None, + separator_block_width: None, + } + } +} diff --git a/src/bar/module/window.rs b/src/bar/module/window.rs new file mode 100644 index 0000000..c05525a --- /dev/null +++ b/src/bar/module/window.rs @@ -0,0 +1,105 @@ +// 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 . + +//! The window `swayrbar` module. + +use crate::bar::module::BarModuleFn; +use std::cell::RefCell; +use swaybar_types as s; +use swayipc as ipc; + +pub struct NodeIter<'a> { + stack: Vec<&'a ipc::Node>, +} + +impl<'a> NodeIter<'a> { + fn new(node: &'a ipc::Node) -> NodeIter { + NodeIter { stack: vec![node] } + } +} + +impl<'a> Iterator for NodeIter<'a> { + type Item = &'a ipc::Node; + + fn next(&mut self) -> Option { + if let Some(node) = self.stack.pop() { + for n in &node.floating_nodes { + self.stack.push(n); + } + for n in &node.nodes { + self.stack.push(n); + } + Some(node) + } else { + None + } + } +} + +pub struct BarModuleWindow { + pub instance: String, + connection: RefCell, +} + +impl BarModuleFn for BarModuleWindow { + fn init() -> Box { + Box::new(BarModuleWindow { + instance: "0".to_string(), + connection: RefCell::new( + ipc::Connection::new() + .expect("Couldn't get a sway IPC connection"), + ), + }) + } + + fn name() -> String { + String::from("window") + } + + fn instance(&self) -> String { + self.instance.clone() + } + + fn build(&self) -> s::Block { + let x: String = match self.connection.borrow_mut().get_tree() { + Ok(root) => { + let o: Option<&ipc::Node> = + NodeIter::new(&root).find(|n| n.focused); + o.map(|w| w.name.clone().unwrap_or_default()) + .unwrap_or_else(String::new) + } + Err(err) => format!("{}", err), + }; + s::Block { + name: Some(Self::name()), + instance: Some(self.instance.clone()), + full_text: x, + align: Some(s::Align::Right), + markup: Some(s::Markup::Pango), + short_text: None, + color: None, + background: None, + border: None, + border_top: None, + border_bottom: None, + border_left: None, + border_right: None, + min_width: None, + urgent: None, + separator: None, + separator_block_width: None, + } + } +} diff --git a/src/bin/swayrbar.rs b/src/bin/swayrbar.rs new file mode 100644 index 0000000..c4cd038 --- /dev/null +++ b/src/bin/swayrbar.rs @@ -0,0 +1,22 @@ +// 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 . + +//! The `swayrbar` binary. + +fn main() { + // TODO: We need a config file cmd line option so that each bar can have + // its own config. + swayr::bar::start(); +} diff --git a/src/fmt_replace.rs b/src/fmt_replace.rs new file mode 100644 index 0000000..58d1870 --- /dev/null +++ b/src/fmt_replace.rs @@ -0,0 +1,71 @@ +// 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 . + +use lazy_static::lazy_static; + +lazy_static! { + pub static ref PLACEHOLDER_RX: regex::Regex = regex::Regex::new( + r"\{(?P[^}:]+)(?::(?P\{[^}]*\})(?P[^}]*))?\}" + ) + .unwrap(); +} + +pub fn maybe_html_escape(do_it: bool, text: String) -> String { + if do_it { + text.replace('<', "<") + .replace('>', ">") + .replace('&', "&") + } else { + text + } +} + +macro_rules! fmt_replace { + ( $fmt_str:expr, $html_escape:ident, + { $( $($pat:pat)|+ => $exp:expr, )+ } + ) => { + $crate::fmt_replace::PLACEHOLDER_RX + .replace_all($fmt_str, |caps: ®ex::Captures| { + let value: String = match &caps["name"] { + $( + $( | $pat )+ => { + let val = $exp; + let fmt_str = caps.name("fmtstr") + .map_or("{}", |m| m.as_str()); + let clipped_str = caps.name("clipstr") + .map_or("", |m| m.as_str()); + $crate::fmt_replace::maybe_html_escape( + $html_escape, + crate::rtfmt::format(fmt_str, &val, clipped_str), + ) + } + )+ + _ => caps[0].to_string(), + }; + value + }).into() + }; +} + +#[test] +fn foo() { + let foo = "{a}, {b}"; + let html_escape = true; + let x: String = fmt_replace!(foo, html_escape, { + "a" => "1".to_string(), + "b" => "2".to_string(), + "c" => "3".to_owned(), + }); +} diff --git a/src/lib.rs b/src/lib.rs index ca73b19..14b3fbe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,10 +23,13 @@ //! window/workspace creations, deletions, and focus changes using sway's JSON //! IPC interface. The client `swayr` offers subcommands, see `swayr --help`. +pub mod bar; pub mod client; pub mod cmds; pub mod config; pub mod demon; +#[macro_use] +pub mod fmt_replace; pub mod layout; pub mod rtfmt; pub mod tree; diff --git a/src/tree.rs b/src/tree.rs index b12a4f9..688a0d4 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -16,7 +16,6 @@ //! 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; @@ -432,20 +431,6 @@ pub fn get_tree<'a>( 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[^}]*))?\}" - ) - .unwrap(); -} - -fn maybe_html_escape(do_it: bool, text: String) -> String { - if do_it { - text.replace('<', "<") - .replace('>', ">") - .replace('&', "&") - } else { - text - } } fn format_marks(marks: &[String]) -> String { @@ -515,36 +500,23 @@ impl DisplayFormat for DisplayNode<'_> { .as_str(), ); - PLACEHOLDER_RX - .replace_all(&fmt, |caps: ®ex::Captures| { - let value = match &caps["name"] { - "id" => self.node.id.to_string(), - "app_name" => self.node.get_app_name().to_string(), - "layout" => format!("{:?}", self.node.layout), - "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()) - .to_string(), - "workspace_name" => self - .tree - .get_parent_node_of_type(self.node.id, Type::Workspace) - .map_or("", |w| w.get_name()) - .to_string(), - "marks" => format_marks(&self.node.marks), - _ => caps[0].to_string(), - }; - let fmt_str = caps.name("fmtstr").map_or("{}", |m| m.as_str()); - let clipped_str = - caps.name("clipstr").map_or("", |m| m.as_str()); - - maybe_html_escape( - html_escape, - rtfmt::format(fmt_str, &value, clipped_str), - ) - }) - .into() + fmt_replace!(&fmt, html_escape, { + "id" => self.node.id.to_string(), + "app_name" => self.node.get_app_name().to_string(), + "layout" => format!("{:?}", self.node.layout), + "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()) + .to_string(), + "workspace_name" => self + .tree + .get_parent_node_of_type(self.node.id, Type::Workspace) + .map_or("", |w| w.get_name()) + .to_string(), + "marks" => format_marks(&self.node.marks), + }) } fn get_indent_level(&self) -> usize {