tmux_lib/
utils.rs

1/// Misc utilities.
2
3pub trait SliceExt {
4    fn trim(&self) -> &Self;
5    fn trim_trailing(&self) -> &Self;
6}
7
8fn is_whitespace(c: &u8) -> bool {
9    *c == b'\t' || *c == b' '
10}
11
12fn is_not_whitespace(c: &u8) -> bool {
13    !is_whitespace(c)
14}
15
16impl SliceExt for [u8] {
17    /// Trim leading and trailing whitespaces (`\t` and ` `) in a `&[u8]`
18    fn trim(&self) -> &[u8] {
19        if let Some(first) = self.iter().position(is_not_whitespace) {
20            if let Some(last) = self.iter().rposition(is_not_whitespace) {
21                &self[first..last + 1]
22            } else {
23                unreachable!();
24            }
25        } else {
26            &[]
27        }
28    }
29
30    /// Trim trailing whitespaces (`\t` and ` `) in a `&[u8]`
31    fn trim_trailing(&self) -> &[u8] {
32        if let Some(last) = self.iter().rposition(is_not_whitespace) {
33            &self[0..last + 1]
34        } else {
35            &[]
36        }
37    }
38}
39
40/// Trim each line of the buffer.
41fn buf_trim_trailing(buf: &[u8]) -> Vec<&[u8]> {
42    let trimmed_lines: Vec<&[u8]> = buf
43        .split(|c| *c == b'\n')
44        .map(SliceExt::trim_trailing) // trim each line
45        .collect();
46
47    trimmed_lines
48}
49
50/// Drop all the last empty lines.
51fn drop_last_empty_lines<'a>(lines: &[&'a [u8]]) -> Vec<&'a [u8]> {
52    if let Some(last) = lines.iter().rposition(|line| !line.is_empty()) {
53        lines[0..=last].to_vec()
54    } else {
55        lines.to_vec()
56    }
57}
58
59/// This function processes a pane captured bufer.
60///
61/// - All lines are trimmed after capture because tmux does not allow capturing escape codes and
62///   trimming lines.
63/// - If `drop_n_last_lines` is greater than 0, the n last line are not captured. This is used only
64///   for panes with a zsh prompt, in order to avoid polluting the history with new prompts on
65///   restore.
66/// - In addition, the last line has an additional ascii reset escape code because tmux does not
67///   capture it.
68///
69pub fn cleanup_captured_buffer(buffer: &[u8], drop_n_last_lines: usize) -> Vec<u8> {
70    let trimmed_lines: Vec<&[u8]> = buf_trim_trailing(buffer);
71    let mut buffer: Vec<&[u8]> = drop_last_empty_lines(&trimmed_lines);
72    buffer.truncate(buffer.len() - drop_n_last_lines);
73
74    // Join the lines with `b'\n'`, add reset code to the last line
75    let mut final_buffer: Vec<u8> = Vec::with_capacity(buffer.len());
76    for (idx, &line) in buffer.iter().enumerate() {
77        final_buffer.extend_from_slice(line);
78
79        let is_last_line = idx == buffer.len() - 1;
80        if is_last_line {
81            let reset = "\u{001b}[0m".as_bytes();
82            final_buffer.extend_from_slice(reset);
83            final_buffer.push(b'\n');
84        } else {
85            final_buffer.push(b'\n');
86        }
87    }
88
89    final_buffer
90}
91
92#[cfg(test)]
93mod tests {
94    use super::{buf_trim_trailing, drop_last_empty_lines, SliceExt};
95
96    #[test]
97    fn trims_trailing_whitespaces() {
98        let input = "  text   ".as_bytes();
99        let expected = "  text".as_bytes();
100
101        let actual = input.trim_trailing();
102        assert_eq!(actual, expected);
103    }
104
105    #[test]
106    fn trims_whitespaces() {
107        let input = "  text   ".as_bytes();
108        let expected = "text".as_bytes();
109
110        let actual = input.trim();
111        assert_eq!(actual, expected);
112    }
113
114    #[test]
115    fn test_buf_trim_trailing() {
116        let text = "line1\n\nline3   ";
117        let actual = buf_trim_trailing(text.as_bytes());
118        let expected = vec!["line1".as_bytes(), "".as_bytes(), "line3".as_bytes()];
119        assert_eq!(actual, expected);
120    }
121
122    #[test]
123    fn test_buf_drop_last_empty_lines() {
124        let text = "line1\nline2\n\nline3   ";
125
126        let trimmed_lines = buf_trim_trailing(text.as_bytes());
127        let actual = drop_last_empty_lines(&trimmed_lines);
128        let expected = trimmed_lines;
129        assert_eq!(actual, expected);
130
131        //
132
133        let text = "line1\nline2\n\n\n     ";
134
135        let trimmed_lines = buf_trim_trailing(text.as_bytes());
136        let actual = drop_last_empty_lines(&trimmed_lines);
137        let expected = vec!["line1".as_bytes(), "line2".as_bytes()];
138        assert_eq!(actual, expected);
139    }
140}