tmux-workspace 0.1.0-alpha.16

Build tmux workspaces from tmuxp-style YAML using libtmux (alpha)
Documentation
use std::collections::VecDeque;

#[cfg(not(any(
    target_os = "cygwin",
    target_os = "emscripten",
    target_os = "fuchsia",
    target_os = "horizon",
    target_os = "netbsd",
    target_os = "openbsd",
    target_os = "redox",
    target_os = "wasi"
)))]
const LINE_BYTES: usize = 8192;

#[derive(Default)]
struct Stream {
    #[cfg(not(any(
        target_os = "cygwin",
        target_os = "emscripten",
        target_os = "fuchsia",
        target_os = "horizon",
        target_os = "netbsd",
        target_os = "openbsd",
        target_os = "redox",
        target_os = "wasi"
    )))]
    parser: anstyle_parse::Parser,
    text: String,
    #[cfg(not(any(
        target_os = "cygwin",
        target_os = "emscripten",
        target_os = "fuchsia",
        target_os = "horizon",
        target_os = "netbsd",
        target_os = "openbsd",
        target_os = "redox",
        target_os = "wasi"
    )))]
    carriage_return: bool,
}

pub(super) struct Tail {
    streams: [Stream; 2],
    completed: VecDeque<String>,
    order: [usize; 2],
    limit: usize,
}

impl Tail {
    pub(super) fn new(limit: usize) -> Self {
        Self {
            streams: Default::default(),
            completed: VecDeque::new(),
            order: [0, 1],
            limit,
        }
    }

    #[cfg(not(any(
        target_os = "cygwin",
        target_os = "emscripten",
        target_os = "fuchsia",
        target_os = "horizon",
        target_os = "netbsd",
        target_os = "openbsd",
        target_os = "redox",
        target_os = "wasi"
    )))]
    pub(super) fn append(&mut self, channel: usize, bytes: &[u8]) {
        let stream = &mut self.streams[channel];
        let mut line = Line {
            text: &mut stream.text,
            carriage_return: &mut stream.carriage_return,
            completed: &mut self.completed,
            limit: self.limit,
        };
        for &byte in bytes {
            stream.parser.advance(&mut line, byte);
        }
        self.order = [1 - channel, channel];
    }

    pub(super) fn visible(&self) -> impl Iterator<Item = &str> {
        let partials: Vec<_> = self
            .order
            .iter()
            .map(|&index| self.streams[index].text.as_str())
            .filter(|text| !text.is_empty())
            .collect();
        self.completed
            .iter()
            .map(String::as_str)
            .chain(partials.iter().copied())
            .skip((self.completed.len() + partials.len()).saturating_sub(self.limit))
            .collect::<Vec<_>>()
            .into_iter()
    }
}

#[cfg(not(any(
    target_os = "cygwin",
    target_os = "emscripten",
    target_os = "fuchsia",
    target_os = "horizon",
    target_os = "netbsd",
    target_os = "openbsd",
    target_os = "redox",
    target_os = "wasi"
)))]
struct Line<'a> {
    text: &'a mut String,
    carriage_return: &'a mut bool,
    completed: &'a mut VecDeque<String>,
    limit: usize,
}

#[cfg(not(any(
    target_os = "cygwin",
    target_os = "emscripten",
    target_os = "fuchsia",
    target_os = "horizon",
    target_os = "netbsd",
    target_os = "openbsd",
    target_os = "redox",
    target_os = "wasi"
)))]
impl anstyle_parse::Perform for Line<'_> {
    fn print(&mut self, character: char) {
        if character.is_control() {
            return;
        }
        if *self.carriage_return {
            self.text.clear();
            *self.carriage_return = false;
        }
        if self.text.len() + character.len_utf8() <= LINE_BYTES {
            self.text.push(character);
        }
    }

    fn execute(&mut self, byte: u8) {
        match byte {
            b'\r' => *self.carriage_return = true,
            b'\n' => {
                self.completed.push_back(std::mem::take(self.text));
                *self.carriage_return = false;
                while self.completed.len() > self.limit {
                    self.completed.pop_front();
                }
            }
            b'\t' => self.print(' '),
            _ => {}
        }
    }
}

#[cfg(all(
    test,
    not(any(
        target_os = "cygwin",
        target_os = "emscripten",
        target_os = "fuchsia",
        target_os = "horizon",
        target_os = "netbsd",
        target_os = "openbsd",
        target_os = "redox",
        target_os = "wasi"
    ))
))]
mod tests {
    use super::*;

    #[test]
    fn chunks_preserve_unicode_crlf_and_stream_boundaries_without_terminal_controls() {
        let mut tail = Tail::new(4);
        for byte in "outå°¾\r\n\x1b[31mred\x1b[0m\x1b]0;title\x07\rreplace".as_bytes() {
            tail.append(0, &[*byte]);
        }
        tail.append(1, b"error\r");
        tail.append(1, b"\nend\ttext");
        assert_eq!(
            tail.visible().collect::<Vec<_>>(),
            ["outå°¾", "error", "replace", "end text"]
        );
    }

    #[test]
    fn tails_bound_long_lines_and_unterminated_osc_and_reset_at_newline() {
        let mut tail = Tail::new(2);
        tail.append(0, &vec![b'x'; LINE_BYTES * 3]);
        assert_eq!(tail.visible().next().map(str::len), Some(LINE_BYTES));
        tail.append(0, b"\nnext\nlast\n");
        assert_eq!(tail.visible().collect::<Vec<_>>(), ["next", "last"]);
        tail.append(1, b"\x1b]0;");
        tail.append(1, &vec![b'x'; LINE_BYTES * 3]);
        tail.append(1, b"\x07visible");
        assert_eq!(tail.visible().collect::<Vec<_>>(), ["last", "visible"]);
    }
}