i3status_rs/formatting/formatter/
str.rs

1use std::iter::repeat;
2use std::time::Instant;
3
4use crate::escape::CollectEscaped;
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.val.parse().error("Width must be a positive integer")?;
40                }
41                "max_width" | "max_w" => {
42                    max_width = arg.val.parse().error("Width must be a positive integer")?;
43                }
44                "width" | "w" => {
45                    min_width = arg.val.parse().error("Width must be a positive integer")?;
46                    max_width = min_width;
47                }
48                "rot_interval" => {
49                    rot_interval = Some(
50                        arg.val
51                            .parse()
52                            .error("Interval must be a positive number")?,
53                    );
54                }
55                "rot_separator" => {
56                    rot_separator = Some(arg.val.to_string());
57                }
58                other => {
59                    return Err(Error::new(format!("Unknown argument for 'str': '{other}'")));
60                }
61            }
62        }
63        if max_width < min_width {
64            return Err(Error::new(
65                "Max width must be greater of equal to min width",
66            ));
67        }
68        if let Some(rot_interval) = rot_interval {
69            if rot_interval < 0.1 {
70                return Err(Error::new("Interval must be greater than 0.1"));
71            }
72        }
73        Ok(StrFormatter {
74            min_width,
75            max_width,
76            rot_interval_ms: rot_interval.map(|x| (x * 1e3) as u64),
77            init_time: Some(Instant::now()),
78            rot_separator,
79        })
80    }
81}
82
83impl Formatter for StrFormatter {
84    fn format(&self, val: &Value, config: &SharedConfig) -> Result<String, FormatError> {
85        match val {
86            Value::Text(text) => {
87                let text: Vec<&str> = text.graphemes(true).collect();
88                let width = text.len();
89                Ok(match (self.rot_interval_ms, self.init_time) {
90                    (Some(rot_interval_ms), Some(init_time)) if width > self.max_width => {
91                        let rot_separator: Vec<&str> = self
92                            .rot_separator
93                            .as_deref()
94                            .unwrap_or("|")
95                            .graphemes(true)
96                            .collect();
97                        let width = width + rot_separator.len(); // Now we include `rot_separator` at the end
98                        let step = (init_time.elapsed().as_millis() as u64 / rot_interval_ms)
99                            as usize
100                            % width;
101                        let w1 = self.max_width.min(width - step);
102                        text.iter()
103                            .chain(rot_separator.iter())
104                            .skip(step)
105                            .take(w1)
106                            .chain(text.iter())
107                            .take(self.max_width)
108                            .collect_pango_escaped()
109                    }
110                    _ => text
111                        .iter()
112                        .chain(repeat(&" ").take(self.min_width.saturating_sub(width)))
113                        .take(self.max_width)
114                        .collect_pango_escaped(),
115                })
116            }
117            Value::Icon(icon, value) => config.get_icon(icon, *value).map_err(Into::into),
118            other => Err(FormatError::IncompatibleFormatter {
119                ty: other.type_name(),
120                fmt: "str",
121            }),
122        }
123    }
124
125    fn interval(&self) -> Option<Duration> {
126        self.rot_interval_ms.map(Duration::from_millis)
127    }
128}