Skip to main content

nested_shell_async/
nested_shell_async.rs

1use std::{
2    io::{self, BufWriter, Read, Write},
3    sync::{Arc, RwLock},
4    time::Duration,
5};
6
7use bytes::Bytes;
8use crossterm::event::{self, Event, KeyCode, KeyEventKind};
9use portable_pty::{CommandBuilder, NativePtySystem, PtySize, PtySystem};
10use ratatui::{
11    DefaultTerminal, Frame,
12    layout::Alignment,
13    style::{Modifier, Style},
14    widgets::{Block, Borders, Paragraph},
15};
16use tokio::{
17    sync::mpsc::{Sender, channel},
18    task,
19};
20use tui_term::widget::PseudoTerminal;
21use vt100::Screen;
22
23#[derive(Debug)]
24struct Size {
25    cols: u16,
26    rows: u16,
27}
28
29#[tokio::main]
30async fn main() -> io::Result<()> {
31    let mut terminal = ratatui::init();
32    let result = run_app(&mut terminal).await;
33    ratatui::restore();
34    result
35}
36
37async fn run_app(terminal: &mut DefaultTerminal) -> io::Result<()> {
38    let pty_system = NativePtySystem::default();
39    let cwd = std::env::current_dir().unwrap();
40    let mut cmd = CommandBuilder::new_default_prog();
41    cmd.cwd(cwd);
42
43    let size = Size {
44        rows: terminal.size()?.height,
45        cols: terminal.size()?.width,
46    };
47
48    let pair = pty_system
49        .openpty(PtySize {
50            rows: size.rows,
51            cols: size.cols,
52            pixel_width: 0,
53            pixel_height: 0,
54        })
55        .unwrap();
56    // Wait for the child to complete
57    task::spawn_blocking(move || {
58        let mut child = pair.slave.spawn_command(cmd).unwrap();
59        let _child_exit_status = child.wait().unwrap();
60        drop(pair.slave);
61    });
62
63    let mut reader = pair.master.try_clone_reader().unwrap();
64    let parser = Arc::new(RwLock::new(vt100::Parser::new(size.rows, size.cols, 0)));
65
66    {
67        let parser = parser.clone();
68        task::spawn_blocking(move || {
69            // Consume the output from the child
70            // Can't read the full buffer, since that would wait for EOF
71            let mut buf = [0u8; 8192];
72            let mut processed_buf = Vec::new();
73            loop {
74                let size = reader.read(&mut buf).unwrap();
75                if size == 0 {
76                    break;
77                }
78                if size > 0 {
79                    processed_buf.extend_from_slice(&buf[..size]);
80                    let mut parser = parser.write().unwrap();
81                    parser.process(&processed_buf);
82
83                    // Clear the processed portion of the buffer
84                    processed_buf.clear();
85                }
86            }
87        });
88    }
89
90    let (tx, mut rx) = channel::<Bytes>(32);
91
92    let mut writer = BufWriter::new(pair.master.take_writer().unwrap());
93
94    // Drop writer on purpose
95    tokio::spawn(async move {
96        while let Some(bytes) = rx.recv().await {
97            writer.write_all(&bytes).unwrap();
98            writer.flush().unwrap();
99        }
100        drop(pair.master);
101    });
102
103    let result = run(terminal, parser, tx).await;
104    println!("{size:?}");
105    result
106}
107
108async fn run(
109    terminal: &mut DefaultTerminal,
110    parser: Arc<RwLock<vt100::Parser>>,
111    sender: Sender<Bytes>,
112) -> io::Result<()> {
113    loop {
114        terminal.draw(|f| ui(f, parser.read().unwrap().screen()))?;
115
116        // Event read is non-blocking
117        if event::poll(Duration::from_millis(10))? {
118            // It's guaranteed that the `read()` won't block when the `poll()`
119            // function returns `true`
120            match event::read()? {
121                Event::Key(key) => {
122                    if key.kind == KeyEventKind::Press {
123                        match key.code {
124                            KeyCode::Char('q') => return Ok(()),
125                            KeyCode::Char(input) => sender
126                                .send(Bytes::from(input.to_string().into_bytes()))
127                                .await
128                                .unwrap(),
129                            KeyCode::Backspace => {
130                                sender.send(Bytes::from(vec![8])).await.unwrap();
131                            }
132                            KeyCode::Enter => sender.send(Bytes::from(vec![b'\n'])).await.unwrap(),
133                            KeyCode::Left => {
134                                sender.send(Bytes::from(vec![27, 91, 68])).await.unwrap()
135                            }
136                            KeyCode::Right => {
137                                sender.send(Bytes::from(vec![27, 91, 67])).await.unwrap()
138                            }
139                            KeyCode::Up => {
140                                sender.send(Bytes::from(vec![27, 91, 65])).await.unwrap()
141                            }
142                            KeyCode::Down => {
143                                sender.send(Bytes::from(vec![27, 91, 66])).await.unwrap()
144                            }
145                            KeyCode::Home => todo!(),
146                            KeyCode::End => todo!(),
147                            KeyCode::PageUp => todo!(),
148                            KeyCode::PageDown => todo!(),
149                            KeyCode::Tab => todo!(),
150                            KeyCode::BackTab => todo!(),
151                            KeyCode::Delete => todo!(),
152                            KeyCode::Insert => todo!(),
153                            KeyCode::F(_) => todo!(),
154                            KeyCode::Null => todo!(),
155                            KeyCode::Esc => todo!(),
156                            KeyCode::CapsLock => todo!(),
157                            KeyCode::ScrollLock => todo!(),
158                            KeyCode::NumLock => todo!(),
159                            KeyCode::PrintScreen => todo!(),
160                            KeyCode::Pause => todo!(),
161                            KeyCode::Menu => todo!(),
162                            KeyCode::KeypadBegin => todo!(),
163                            KeyCode::Media(_) => todo!(),
164                            KeyCode::Modifier(_) => todo!(),
165                        }
166                    }
167                }
168                Event::FocusGained => {}
169                Event::FocusLost => {}
170                Event::Mouse(_) => {}
171                Event::Paste(_) => todo!(),
172                Event::Resize(cols, rows) => {
173                    parser.write().unwrap().screen_mut().set_size(rows, cols);
174                }
175            }
176        }
177    }
178}
179
180fn ui(f: &mut Frame, screen: &Screen) {
181    let chunks = ratatui::layout::Layout::default()
182        .direction(ratatui::layout::Direction::Vertical)
183        .margin(1)
184        .constraints(
185            [
186                ratatui::layout::Constraint::Percentage(100),
187                ratatui::layout::Constraint::Min(1),
188            ]
189            .as_ref(),
190        )
191        .split(f.area());
192    let block = Block::default()
193        .borders(Borders::ALL)
194        .style(Style::default().add_modifier(Modifier::BOLD));
195    let pseudo_term = PseudoTerminal::new(screen).block(block);
196    f.render_widget(pseudo_term, chunks[0]);
197    let explanation = "Press q to exit".to_string();
198    let explanation = Paragraph::new(explanation)
199        .style(Style::default().add_modifier(Modifier::BOLD | Modifier::REVERSED))
200        .alignment(Alignment::Center);
201    f.render_widget(explanation, chunks[1]);
202}