Spawning commands

This commit is contained in:
Thomas Forgione 2023-10-18 23:16:03 +02:00
parent 1c75b09d0d
commit 4295c93435
3 changed files with 145 additions and 15 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
/target /target
log.txt

13
demo.sh Executable file
View File

@ -0,0 +1,13 @@
#!/usr/bin/env bash
rand() {
shuf -i"$1"-"$2" -n1
}
iterations=$(rand 5 10)
for i in $(seq 1 "$iterations"); do
color="\x1B[3$(rand 0 6)m"
echo -e "$color$(rand 1 100)\x1b[0m"
sleep $(rand 1 2)
done

View File

@ -1,5 +1,7 @@
use std::env; use std::io::{self, stdin, stdout, BufRead, BufReader, Write};
use std::io::{self, stdin, stdout, Write}; use std::process::{Command, Stdio};
use std::sync::mpsc::{channel, Sender};
use std::{env, thread};
use termion::event::{Event, Key, MouseEvent}; use termion::event::{Event, Key, MouseEvent};
use termion::input::{MouseTerminal, TermRead}; use termion::input::{MouseTerminal, TermRead};
@ -13,16 +15,76 @@ use termion::{clear, color, cursor, style};
pub struct Tile { pub struct Tile {
/// The command that should be executed in the tile. /// The command that should be executed in the tile.
pub command: Vec<String>, pub command: Vec<String>,
/// Content of the command's stdout and stderr.
///
/// We put both stdout and stderr here to avoid dealing with order between stdout and stderr.
pub stdout: Vec<String>,
} }
impl Tile { impl Tile {
/// Creates a new empty tile. /// Creates a new empty tile.
pub fn new(command: &[String]) -> Tile { pub fn new(command: &[String], i: u16, j: u16, sender: Sender<Msg>) -> Tile {
let command = command
.into_iter()
.map(|x| x.to_string())
.collect::<Vec<_>>();
let clone = command.clone();
thread::spawn(move || {
let mut child = Command::new(&clone[0])
.args(&clone[1..])
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
let stdout = child.stdout.take().unwrap();
let reader = BufReader::new(stdout);
let mut lines = reader.lines();
loop {
match lines.next() {
Some(Ok(line)) => {
sender.send(Msg::Stdout(i, j, line)).unwrap();
}
Some(Err(_)) => {
break;
}
None => break,
}
}
sender.send(Msg::Stdout(i, j, String::new())).unwrap();
let code = child.wait().unwrap().code().unwrap();
let exit_string = format!(
"{}{}Command exited with return code {}{}{}",
style::Bold,
if code == 0 {
color::Green.fg_str()
} else {
color::Red.fg_str()
},
code,
style::Reset,
color::Reset.fg_str()
);
sender.send(Msg::Stdout(i, j, exit_string)).unwrap();
});
Tile { Tile {
command: command command: command
.into_iter() .into_iter()
.map(|x| x.to_string()) .map(|x| x.to_string())
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
stdout: vec![],
} }
} }
} }
@ -66,6 +128,11 @@ impl<W: Write> Multiview<W> {
&self.tiles[i as usize][j as usize] &self.tiles[i as usize][j as usize]
} }
/// Helper to easily access a mut tile.
pub fn tile_mut(&mut self, (i, j): (u16, u16)) -> &mut Tile {
&mut self.tiles[i as usize][j as usize]
}
/// Sets the selected tile from (x, y) coordinates. /// Sets the selected tile from (x, y) coordinates.
pub fn select_tile(&mut self, (x, y): (u16, u16), term_size: (u16, u16)) { pub fn select_tile(&mut self, (x, y): (u16, u16), term_size: (u16, u16)) {
let w = term_size.0 / self.tiles[0].len() as u16; let w = term_size.0 / self.tiles[0].len() as u16;
@ -129,6 +196,15 @@ impl<W: Write> Multiview<W> {
write!(self.stdout, "{}┤", cursor::Goto(x2, y1 + 2))?; write!(self.stdout, "{}┤", cursor::Goto(x2, y1 + 2))?;
let tile = &self.tile((i, j)); let tile = &self.tile((i, j));
let command_str = tile.command.join(" ");
// TODO: find a way to avoid this copy
let lines = tile
.stdout
.iter()
.map(|x| x.to_string())
.enumerate()
.collect::<Vec<_>>();
write!( write!(
self.stdout, self.stdout,
@ -136,10 +212,19 @@ impl<W: Write> Multiview<W> {
color::Reset.fg_str(), color::Reset.fg_str(),
cursor::Goto(x1 + 1, y1 + 1), cursor::Goto(x1 + 1, y1 + 1),
style::Bold, style::Bold,
tile.command.join(" "), command_str,
style::Reset, style::Reset,
)?; )?;
for (line_index, line) in lines {
write!(
self.stdout,
"{}{}",
cursor::Goto(x1 + 2, y1 + 3 + line_index as u16),
line
)?;
}
Ok(()) Ok(())
} }
@ -165,14 +250,34 @@ impl<W: Write> Drop for Multiview<W> {
} }
} }
/// An event that can be sent in channels.
pub enum Msg {
/// An stdout line arrived.
Stdout(u16, u16, String),
/// A click occured.
Click(u16, u16),
/// The program was asked to exit.
Exit,
}
/// Starts the multiview application. /// Starts the multiview application.
pub fn main() -> io::Result<()> { pub fn main() -> io::Result<()> {
let (sender, receiver) = channel();
let args = env::args().skip(1).collect::<Vec<_>>(); let args = env::args().skip(1).collect::<Vec<_>>();
let tiles = args let tiles = args
.split(|x| x == "//") .split(|x| x == "//")
.map(|x| x.split(|y| y == "::").map(Tile::new)) .map(|x| x.split(|y| y == "::").enumerate().collect::<Vec<_>>())
.map(|x| x.collect::<Vec<_>>()) .enumerate()
.map(|(i, tiles)| {
tiles
.into_iter()
.map(|(j, tile)| Tile::new(tile, i as u16, j as u16, sender.clone()))
.collect::<Vec<_>>()
})
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let stdin = stdin(); let stdin = stdin();
@ -185,18 +290,29 @@ pub fn main() -> io::Result<()> {
let mut multiview = Multiview::new(stdout, tiles)?; let mut multiview = Multiview::new(stdout, tiles)?;
multiview.render(term_size)?; multiview.render(term_size)?;
thread::spawn(move || {
for c in stdin.events() { for c in stdin.events() {
let evt = c?; let evt = c.unwrap();
match evt { match evt {
Event::Key(Key::Char('q')) => break, Event::Key(Key::Char('q')) => sender.send(Msg::Exit).unwrap(),
Event::Mouse(me) => match me { Event::Mouse(me) => match me {
MouseEvent::Press(_, x, y) => multiview.select_tile((x, y), term_size), MouseEvent::Press(_, x, y) => sender.send(Msg::Click(x, y)).unwrap(),
_ => (), _ => (),
}, },
_ => {} _ => {}
} }
}
});
loop {
match receiver.recv() {
Ok(Msg::Stdout(i, j, line)) => multiview.tile_mut((i, j)).stdout.push(line),
Ok(Msg::Click(x, y)) => multiview.select_tile((x, y), term_size),
Ok(Msg::Exit) => break,
Err(_) => (),
}
multiview.render(term_size)?; multiview.render(term_size)?;
} }