summaryrefslogtreecommitdiffstats
path: root/src/history.rs
blob: bf89ec1e7b9bc6758503876760e2e6df527b2c3c (plain) (blame)
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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
use async_std::io::ReadExt as _;
use pty_process::Command as _;
use textmode::Textmode as _;

pub struct History {
    entries: Vec<async_std::sync::Arc<async_std::sync::Mutex<HistoryEntry>>>,
    action: async_std::channel::Sender<crate::nbsh::Action>,
}

impl History {
    pub fn new(
        action: async_std::channel::Sender<crate::nbsh::Action>,
    ) -> Self {
        Self {
            entries: vec![],
            action,
        }
    }

    pub async fn run(&mut self, cmd: &str) -> anyhow::Result<usize> {
        let (exe, args) = parse_cmd(cmd);
        let mut process = async_process::Command::new(&exe);
        process.args(&args);
        let child = process
            .spawn_pty(Some(&pty_process::Size::new(24, 80)))
            .unwrap();
        let entry = async_std::sync::Arc::new(async_std::sync::Mutex::new(
            HistoryEntry::new(cmd),
        ));
        let task_entry = async_std::sync::Arc::clone(&entry);
        let task_action = self.action.clone();
        async_std::task::spawn(async move {
            loop {
                let mut buf = [0_u8; 4096];
                match child.pty().read(&mut buf).await {
                    Ok(bytes) => {
                        task_entry.lock_arc().await.vt.process(&buf[..bytes]);
                    }
                    Err(e) => {
                        if e.raw_os_error() != Some(libc::EIO) {
                            eprintln!("pty read failed: {:?}", e);
                        }
                        task_entry.lock_arc().await.running = false;
                        task_action
                            .send(crate::nbsh::Action::Render)
                            .await
                            .unwrap();
                        break;
                    }
                }
                task_action.send(crate::nbsh::Action::Render).await.unwrap();
            }
        });
        self.entries.push(entry);
        self.action.send(crate::nbsh::Action::Render).await.unwrap();
        Ok(self.entries.len() - 1)
    }

    pub async fn handle_key(
        &mut self,
        key: textmode::Key,
        idx: usize,
    ) -> bool {
        if let textmode::Key::Bytes(b) = key {
            self.send_process_input(idx, &b).await.unwrap();
        } else {
            unreachable!();
        }
        false
    }

    pub async fn render(
        &self,
        out: &mut textmode::Output,
        repl_lines: usize,
    ) -> anyhow::Result<()> {
        let mut used_lines = repl_lines;
        for entry in self.entries.iter().rev() {
            let entry = entry.lock_arc().await;
            let screen = entry.vt.screen();
            let mut last_row = 0;
            for (idx, row) in screen.rows(0, 80).enumerate() {
                if !row.is_empty() {
                    last_row = idx + 1;
                }
            }
            used_lines += 1 + std::cmp::min(6, last_row);
            if used_lines > 24 {
                break;
            }
            out.move_to((24 - used_lines).try_into().unwrap(), 0);
            out.write_str("$ ");
            if entry.running {
                out.set_bgcolor(vt100::Color::Rgb(16, 64, 16));
            }
            out.write_str(&entry.cmd);
            out.reset_attributes();
            out.write(b"\r\n");
            if last_row > 5 {
                out.set_bgcolor(textmode::color::RED);
                out.write(b"...");
                out.reset_attributes();
                out.write(b"\r\n");
            }
            for row in screen
                .rows_formatted(0, 80)
                .take(last_row)
                .skip(last_row.saturating_sub(5))
            {
                out.write(&row);
                out.write(b"\r\n");
            }
            out.reset_attributes();
        }
        Ok(())
    }

    async fn send_process_input(
        &self,
        idx: usize,
        input: &[u8],
    ) -> anyhow::Result<()> {
        todo!()
    }
}

struct HistoryEntry {
    cmd: String,
    vt: vt100::Parser,
    running: bool, // option end time
                   // start time
}

impl HistoryEntry {
    fn new(cmd: &str) -> Self {
        Self {
            cmd: cmd.into(),
            vt: vt100::Parser::new(24, 80, 0),
            running: true,
        }
    }
}

fn parse_cmd(full_cmd: &str) -> (String, Vec<String>) {
    let mut parts = full_cmd.split(' ');
    let cmd = parts.next().unwrap();
    (
        cmd.to_string(),
        parts.map(std::string::ToString::to_string).collect(),
    )
}