diff --git a/config.toml b/config.toml index 61e15c7..25da140 100644 --- a/config.toml +++ b/config.toml @@ -19,4 +19,17 @@ command = "cat fkasdjfölashjdflksd" command = "touch uwu.txt" [side-effect-remove] -command = "rm uwu.txt" \ No newline at end of file +command = "rm uwu.txt" + +[compile] +command = "cargo check" + +[loop] +command = """ +counter=0 + +while true; do + echo $counter + counter=$((counter+1)) +done +""" \ No newline at end of file diff --git a/src/controller.rs b/src/controller.rs index d5b6e0c..a273637 100644 --- a/src/controller.rs +++ b/src/controller.rs @@ -1,5 +1,5 @@ use crate::model::config::Config; -use crate::model::{AppState, Service, ServiceStatus, SmError, SmResult}; +use crate::model::{AppState, Service, ServiceStatus, SmError, SmResult, StdIoStream}; use crate::{view, App}; use crossterm::event; use crossterm::event::{Event, KeyCode}; @@ -9,13 +9,14 @@ use std::io::{ErrorKind, Read, Write}; use std::process::{Child, Command, Stdio}; use std::sync::mpsc::TryRecvError; use std::sync::{mpsc, Mutex}; +use std::time::Duration; use tui::backend::Backend; use tui::widgets::TableState; use tui::Terminal; -const STDOUT_SEND_BUF_SIZE: usize = 512; +const STDIO_SEND_BUF_SIZE: usize = 512; -pub type StdoutSendBuf = ([u8; STDOUT_SEND_BUF_SIZE], usize); +pub type StdioSendBuf = ([u8; STDIO_SEND_BUF_SIZE], usize); pub fn run_app(terminal: &mut Terminal, mut app: App) -> SmResult { loop { @@ -23,18 +24,20 @@ pub fn run_app(terminal: &mut Terminal, mut app: App) -> SmResult app.recv_stdouts(); - if let Event::Key(key) = event::read()? { - match key.code { - KeyCode::Char('q') => match app.selected { - Some(_) => app.leave_service(), - None => break, - }, - KeyCode::Char('r') => app.run_service()?, - KeyCode::Down => app.next(), - KeyCode::Up => app.previous(), - KeyCode::Enter => app.select_service(), - KeyCode::Esc => app.leave_service(), - _ => {} + if event::poll(Duration::from_millis(10))? { + if let Event::Key(key) = event::read()? { + match key.code { + KeyCode::Char('q') => match app.selected { + Some(_) => app.leave_service(), + None => break, + }, + KeyCode::Char('r') => app.run_service()?, + KeyCode::Down => app.next(), + KeyCode::Up => app.previous(), + KeyCode::Enter => app.select_service(), + KeyCode::Esc => app.leave_service(), + _ => {} + } } } } @@ -66,9 +69,11 @@ impl App { .or_else(|_| std::env::current_dir())?, env: service.env.unwrap_or_else(HashMap::new), status: Mutex::new(ServiceStatus::NotStarted), - stdout_buf: Vec::new(), - stdout_recv, - stdout_send: Mutex::new(Some(stdout_send)), + std_io_buf: Vec::new(), + stdout: StdIoStream { + recv: stdout_recv, + send: stdout_send, + }, }) }) .collect::>()?, @@ -84,8 +89,12 @@ impl App { fn recv_stdouts(&mut self) { for service in self.table.services.iter_mut() { - while let Ok((buf, n)) = service.stdout_recv.try_recv() { - service.stdout_buf.extend_from_slice(&buf[0..n]); + while let Ok((buf, n)) = service.stdout.recv.try_recv() { + service.std_io_buf.extend(&buf[0..n]); + + if service.std_io_buf.len() > 2_000_000 { + service.std_io_buf.clear(); // todo don't + } } } } @@ -131,13 +140,20 @@ impl App { } fn run_service(&mut self) -> SmResult { - if let Some(selected) = self.selected { - self.start_service(selected) - } else if let Some(selected) = self.table.table_state.selected() { - self.start_service(selected) - } else { - Ok(()) + let index = self.selected.or_else(|| self.table.table_state.selected()); + + if let Some(index) = index { + let status = { + let service = &mut self.table.services[index]; + *service.status.lock()? + }; + + if status != ServiceStatus::Running { + self.start_service(index)?; + } } + + Ok(()) } fn start_service(&mut self, service: usize) -> SmResult { @@ -145,12 +161,6 @@ impl App { *service.status.lock()? = ServiceStatus::Running; - let stdout_send = service - .stdout_send - .lock()? - .take() - .ok_or(SmError::StdioStolen)?; - let mut cmd = Command::new("sh"); cmd.args(["-c", &service.command]); @@ -160,9 +170,11 @@ impl App { cmd.stderr(Stdio::piped()); cmd.stdin(Stdio::piped()); + let stdout_send = service.stdout.send.clone(); + let child = match cmd.spawn() { Err(err) => { - let mut buf = [0; STDOUT_SEND_BUF_SIZE]; + let mut buf = [0; STDIO_SEND_BUF_SIZE]; let bytes = err.to_string(); @@ -177,13 +189,15 @@ impl App { Ok(child) => child, }; - let (tx, rx) = mpsc::channel(); + let (terminate_send, terminate_recv) = mpsc::channel(); - self.thread_terminates.push(tx); + self.thread_terminates.push(terminate_send); - std::thread::spawn(move || match child_process_thread(child, stdout_send, rx) { - Ok(_) => {} - Err(e) => std::fs::write("error.txt", e.to_string()).unwrap(), + std::thread::spawn(move || { + match child_process_thread(child, stdout_send, terminate_recv) { + Ok(_) => {} + Err(e) => std::fs::write("error.txt", e.to_string()).unwrap(), + } }); Ok(()) @@ -191,7 +205,7 @@ impl App { } fn child_process_thread( child: Child, - stdout_send: mpsc::Sender, + stdout_send: mpsc::Sender, terminate_channel: mpsc::Receiver<()>, ) -> io::Result<()> { let mut child = child; @@ -203,7 +217,7 @@ fn child_process_thread( Err(TryRecvError::Empty) => {} } - let mut stdout_buf = [0; STDOUT_SEND_BUF_SIZE]; + let mut stdout_buf = [0; STDIO_SEND_BUF_SIZE]; match stdout.read(&mut stdout_buf) { Ok(0) => continue, diff --git a/src/model.rs b/src/model.rs index aef3baa..84446fb 100644 --- a/src/model.rs +++ b/src/model.rs @@ -1,4 +1,4 @@ -use crate::controller::StdoutSendBuf; +use crate::controller::StdioSendBuf; use std::collections::HashMap; use std::path::PathBuf; use std::sync::{mpsc, Mutex}; @@ -26,12 +26,17 @@ pub struct Service { pub workdir: PathBuf, pub env: HashMap, pub status: Mutex, - pub stdout_buf: Vec, - pub stdout_recv: mpsc::Receiver, - pub stdout_send: Mutex>>, + pub std_io_buf: Vec, + pub stdout: StdIoStream, } #[derive(Debug)] +pub struct StdIoStream { + pub recv: mpsc::Receiver, + pub send: mpsc::Sender, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[allow(dead_code)] pub enum ServiceStatus { NotStarted, @@ -65,7 +70,6 @@ mod error { Io(io::Error), FailedToStartChild(io::Error), MutexPoisoned, - StdioStolen, FailedToSendStdio, } @@ -86,7 +90,6 @@ mod error { match self { Self::Io(e) => Display::fmt(e, f), SmError::MutexPoisoned => f.write_str("Mutex was poisoned. This is a bug."), - SmError::StdioStolen => f.write_str("Stdio was stolen. This is a bug."), SmError::FailedToStartChild(e) => write!(f, "Failed to start child process: {}", e), SmError::FailedToSendStdio => { f.write_str("Failed to send stdio to display thread. This is a bug.") diff --git a/src/view.rs b/src/view.rs index 5b1a302..38c058f 100644 --- a/src/view.rs +++ b/src/view.rs @@ -41,9 +41,14 @@ fn render_full_view(f: &mut Frame, state: &mut AppState, index: u .borders(Borders::ALL) .title("service".as_ref()); - let stdout = String::from_utf8_lossy(&service.stdout_buf); + let len = service.std_io_buf.len(); + let stdout = if len > 10000 { + String::from_utf8_lossy(&service.std_io_buf[len - 10000..len]) + } else { + String::from_utf8_lossy(&service.std_io_buf) + }; - let paragraph = Paragraph::new(stdout.as_ref()).block(block); + let paragraph = Paragraph::new(stdout.as_ref()).block(block.clone()); f.render_widget(paragraph, area) }