i3status_rs/formatting/formatter/
str.rs

1use std::iter::repeat_n;
2use std::time::Instant;
3
4use crate::escape::CollectEscaped as _;
5
6use super::*;
7
8const DEFAULT_STR_MIN_WIDTH: usize = 0;
9const DEFAULT_STR_MAX_WIDTH: usize = usize::MAX;
10const DEFAULT_STR_ROT_INTERVAL: Option<f64> = None;
11const DEFAULT_STR_ROT_SEP: Option<String> = None;
12
13pub const DEFAULT_STRING_FORMATTER: StrFormatter = StrFormatter {
14    min_width: DEFAULT_STR_MIN_WIDTH,
15    max_width: DEFAULT_STR_MAX_WIDTH,
16    rot_interval_ms: None,
17    init_time: None,
18    rot_separator: None,
19};
20
21#[derive(Debug)]
22pub struct StrFormatter {
23    min_width: usize,
24    max_width: usize,
25    rot_interval_ms: Option<u64>,
26    init_time: Option<Instant>,
27    rot_separator: Option<String>,
28}
29
30impl StrFormatter {
31    pub(super) fn from_args(args: &[Arg]) -> Result<Self> {
32        let mut min_width = DEFAULT_STR_MIN_WIDTH;
33        let mut max_width = DEFAULT_STR_MAX_WIDTH;
34        let mut rot_interval = DEFAULT_STR_ROT_INTERVAL;
35        let mut rot_separator = DEFAULT_STR_ROT_SEP;
36        for arg in args {
37            match arg.key {
38                "min_width" | "min_w" => {
39                    min_width = arg.parse_value()?;
40                }
41                "max_width" | "max_w" => {
42                    max_width = arg.parse_value()?;
43                }
44                "width" | "w" => {
45                    min_width = arg.parse_value()?;
46                    max_width = min_width;
47                }
48                "rot_interval" => {
49                    rot_interval = Some(arg.parse_value()?);
50                }
51                "rot_separator" => {
52                    rot_separator = Some(arg.parse_value()?);
53                }
54                other => {
55                    return Err(Error::new(format!("Unknown argument for 'str': '{other}'")));
56                }
57            }
58        }
59        if max_width < min_width {
60            return Err(Error::new(
61                "Max width must be greater of equal to min width",
62            ));
63        }
64        if let Some(rot_interval) = rot_interval {
65            if rot_interval < 0.1 {
66                return Err(Error::new("Interval must be greater than 0.1"));
67            }
68        }
69        Ok(StrFormatter {
70            min_width,
71            max_width,
72            rot_interval_ms: rot_interval.map(|x| (x * 1e3) as u64),
73            init_time: Some(Instant::now()),
74            rot_separator,
75        })
76    }
77}
78
79impl Formatter for StrFormatter {
80    fn format(&self, val: &Value, config: &SharedConfig) -> Result<String, FormatError> {
81        match val {
82            Value::Text(text) => {
83                let text: Vec<&str> = text.graphemes(true).collect();
84                let width = text.len();
85                Ok(match (self.rot_interval_ms, self.init_time) {
86                    (Some(rot_interval_ms), Some(init_time)) if width > self.max_width => {
87                        let rot_separator: Vec<&str> = self
88                            .rot_separator
89                            .as_deref()
90                            .unwrap_or("|")
91                            .graphemes(true)
92                            .collect();
93                        let width = width + rot_separator.len(); // Now we include `rot_separator` at the end
94                        let step = (init_time.elapsed().as_millis() as u64 / rot_interval_ms)
95                            as usize
96                            % width;
97                        let w1 = self.max_width.min(width - step);
98                        text.iter()
99                            .chain(rot_separator.iter())
100                            .skip(step)
101                            .take(w1)
102                            .chain(text.iter())
103                            .take(self.max_width)
104                            .collect_pango_escaped()
105                    }
106                    _ => text
107                        .iter()
108                        .chain(repeat_n(&" ", self.min_width.saturating_sub(width)))
109                        .take(self.max_width)
110                        .collect_pango_escaped(),
111                })
112            }
113            Value::Icon(icon, value) => config.get_icon(icon, *value).map_err(Into::into),
114            other => Err(FormatError::IncompatibleFormatter {
115                ty: other.type_name(),
116                fmt: "str",
117            }),
118        }
119    }
120
121    fn interval(&self) -> Option<Duration> {
122        self.rot_interval_ms.map(Duration::from_millis)
123    }
124}