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
//! This is an unstable interface, and can be activatet with the following
//! feature flag: `unstable`.
//!
//! The controller aims to help manage spawning and reading processes
//! to simplify the usage of `tui-term`, with the tradeoff being less flexible.
//!
//! Please do test this interface out and submit feedback, improvements and bug reports.
//!
//!
//! Currently only oneshot commands are supported by the controller:
//! Commands like `ls`, `cat`.
//! Commands like `htop`, that are persistent still need to be handled manually,
//! please look at the examples for a better overview.

use std::{
    io::Result as IoResult,
    sync::{Arc, RwLock},
};

use portable_pty::{CommandBuilder, ExitStatus, PtySystem};
use vt100::{Parser, Screen};

/// Controller, in charge of command dispatch
pub struct Controller {
    // Needs to be set
    cmd: CommandBuilder,
    size: Size,
    parser: Option<Arc<RwLock<Parser>>>,
    exit_status: Option<IoResult<ExitStatus>>,
}

impl Controller {
    pub fn new(cmd: CommandBuilder, size: Option<Size>) -> Self {
        Self {
            cmd,
            size: size.unwrap_or_default(),
            parser: None,
            exit_status: None,
        }
    }

    /// This function is blocking while waiting for the command to end.
    pub fn run(&mut self) {
        let pair = self.init_pty();
        let mut child = pair.slave.spawn_command(self.cmd.clone()).unwrap();
        drop(pair.slave);
        let mut reader = pair.master.try_clone_reader().unwrap();
        let parser = Arc::new(RwLock::new(vt100::Parser::new(
            self.size.rows,
            self.size.cols,
            0,
        )));
        {
            let parser = parser.clone();
            std::thread::spawn(move || {
                // Consume the output from the child
                let mut s = String::new();
                reader.read_to_string(&mut s).unwrap();
                if !s.is_empty() {
                    let mut parser = parser.write().unwrap();
                    parser.process(s.as_bytes());
                }
            });
        }
        // Wait for the child to complete
        self.exit_status = Some(child.wait());
        // Drop writer on purpose
        let _writer = pair.master.take_writer().unwrap();

        drop(pair.master);
        self.parser = Some(parser);
    }

    fn init_pty(&self) -> portable_pty::PtyPair {
        use portable_pty::{NativePtySystem, PtySize};
        let pty_system = NativePtySystem::default();

        pty_system
            .openpty(PtySize {
                rows: self.size.rows,
                cols: self.size.cols,
                pixel_width: self.size.pixel_width,
                pixel_height: self.size.pixel_height,
            })
            .unwrap()
    }

    pub fn screen(&self) -> Option<Screen> {
        if let Some(parser) = &self.parser {
            // We convert the read error into an option, since we might call
            // the read multiple times, but we only care that we can read at some point
            let binding = parser.read().ok()?;
            Some(binding.screen().clone())
        } else {
            None
        }
    }

    /// Whether the command finished running
    pub fn finished(&self) -> bool {
        self.exit_status.is_some()
    }

    /// The exit status of the process
    pub fn status(&self) -> Option<&IoResult<ExitStatus>> {
        self.exit_status.as_ref()
    }
}

#[derive(Default, Clone)]
pub struct Size {
    pub cols: u16,
    pub rows: u16,
    pixel_width: u16,
    pixel_height: u16,
}

impl Size {
    pub fn new(cols: u16, rows: u16, pixel_width: u16, pixel_height: u16) -> Self {
        Self {
            cols,
            rows,
            pixel_width,
            pixel_height,
        }
    }
}