i3status_rs/formatting/formatter/
str.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
use std::iter::repeat;
use std::time::Instant;

use crate::escape::CollectEscaped;

use super::*;

const DEFAULT_STR_MIN_WIDTH: usize = 0;
const DEFAULT_STR_MAX_WIDTH: usize = usize::MAX;
const DEFAULT_STR_ROT_INTERVAL: Option<f64> = None;
const DEFAULT_STR_ROT_SEP: Option<String> = None;

pub const DEFAULT_STRING_FORMATTER: StrFormatter = StrFormatter {
    min_width: DEFAULT_STR_MIN_WIDTH,
    max_width: DEFAULT_STR_MAX_WIDTH,
    rot_interval_ms: None,
    init_time: None,
    rot_separator: None,
};

#[derive(Debug)]
pub struct StrFormatter {
    min_width: usize,
    max_width: usize,
    rot_interval_ms: Option<u64>,
    init_time: Option<Instant>,
    rot_separator: Option<String>,
}

impl StrFormatter {
    pub(super) fn from_args(args: &[Arg]) -> Result<Self> {
        let mut min_width = DEFAULT_STR_MIN_WIDTH;
        let mut max_width = DEFAULT_STR_MAX_WIDTH;
        let mut rot_interval = DEFAULT_STR_ROT_INTERVAL;
        let mut rot_separator = DEFAULT_STR_ROT_SEP;
        for arg in args {
            match arg.key {
                "min_width" | "min_w" => {
                    min_width = arg.val.parse().error("Width must be a positive integer")?;
                }
                "max_width" | "max_w" => {
                    max_width = arg.val.parse().error("Width must be a positive integer")?;
                }
                "width" | "w" => {
                    min_width = arg.val.parse().error("Width must be a positive integer")?;
                    max_width = min_width;
                }
                "rot_interval" => {
                    rot_interval = Some(
                        arg.val
                            .parse()
                            .error("Interval must be a positive number")?,
                    );
                }
                "rot_separator" => {
                    rot_separator = Some(arg.val.to_string());
                }
                other => {
                    return Err(Error::new(format!("Unknown argument for 'str': '{other}'")));
                }
            }
        }
        if max_width < min_width {
            return Err(Error::new(
                "Max width must be greater of equal to min width",
            ));
        }
        if let Some(rot_interval) = rot_interval {
            if rot_interval < 0.1 {
                return Err(Error::new("Interval must be greater than 0.1"));
            }
        }
        Ok(StrFormatter {
            min_width,
            max_width,
            rot_interval_ms: rot_interval.map(|x| (x * 1e3) as u64),
            init_time: Some(Instant::now()),
            rot_separator,
        })
    }
}

impl Formatter for StrFormatter {
    fn format(&self, val: &Value, config: &SharedConfig) -> Result<String, FormatError> {
        match val {
            Value::Text(text) => {
                let text: Vec<&str> = text.graphemes(true).collect();
                let width = text.len();
                Ok(match (self.rot_interval_ms, self.init_time) {
                    (Some(rot_interval_ms), Some(init_time)) if width > self.max_width => {
                        let rot_separator: Vec<&str> = self
                            .rot_separator
                            .as_deref()
                            .unwrap_or("|")
                            .graphemes(true)
                            .collect();
                        let width = width + rot_separator.len(); // Now we include `rot_separator` at the end
                        let step = (init_time.elapsed().as_millis() as u64 / rot_interval_ms)
                            as usize
                            % width;
                        let w1 = self.max_width.min(width - step);
                        text.iter()
                            .chain(rot_separator.iter())
                            .skip(step)
                            .take(w1)
                            .chain(text.iter())
                            .take(self.max_width)
                            .collect_pango_escaped()
                    }
                    _ => text
                        .iter()
                        .chain(repeat(&" ").take(self.min_width.saturating_sub(width)))
                        .take(self.max_width)
                        .collect_pango_escaped(),
                })
            }
            Value::Icon(icon, value) => config.get_icon(icon, *value).map_err(Into::into),
            other => Err(FormatError::IncompatibleFormatter {
                ty: other.type_name(),
                fmt: "str",
            }),
        }
    }

    fn interval(&self) -> Option<Duration> {
        self.rot_interval_ms.map(Duration::from_millis)
    }
}