Compare commits
15 Commits
14c6db5769
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| b7a5d83faa | |||
| 20757a093b | |||
| 7e4d0e05d8 | |||
| b9c31c2251 | |||
| dc950ad0d4 | |||
| 79f03b27bf | |||
| 2e771773b6 | |||
| 093845a4c6 | |||
| 6121dc4224 | |||
| e3d891c522 | |||
| ce8b4fae68 | |||
| 265208db3a | |||
| 0f4211005e | |||
| 922ba9c524 | |||
| 9802c8e740 |
60
README.md
Normal file
60
README.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# multiview-rs
|
||||
|
||||
*Run many commands and watch all outputs in a single terminal*
|
||||
|
||||

|
||||
|
||||
## Installation
|
||||
|
||||
If rust is not already installed, [install rust](https://www.rust-lang.org/tools/install).
|
||||
|
||||
Then run:
|
||||
|
||||
```sh
|
||||
cargo install --git https://gitea.tforgione.fr/tforgione/multiview
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Split your terminal in two rows, the first containing three columns, and the second containing one column:
|
||||
```sh
|
||||
multiview cmd1 :: cmd2 :: cmd3 // cmd4 :: cmd5
|
||||
```
|
||||
|
||||

|
||||
|
||||
Split your terminal in two columns, the first containing three rows, and the second containing one row:
|
||||
```sh
|
||||
multiview cmd1 // cmd2 // cmd3 :: cmd4 // cmd5
|
||||
```
|
||||
|
||||

|
||||
|
||||
## Colors
|
||||
|
||||
Most well written programs will disable colors when running from multiview, in order to force them to use colors, you
|
||||
can use the `unbuffer` command from the [`expect` package](https://packages.ubuntu.com/search?keywords=expect).
|
||||
|
||||
```sh
|
||||
multiview unbuffer cmd1 :: unbuffer cmd2
|
||||
```
|
||||
|
||||
## Shortcuts
|
||||
|
||||
- `k`: kills the current tile
|
||||
- `K`: kills all tiles
|
||||
- `r`: restarts the current tile
|
||||
- `R`: restarts all tiles
|
||||
- `l`: draw a line on the current tile
|
||||
- `L`: draw a line on all tiles
|
||||
- `q`: quits
|
||||
|
||||
## History
|
||||
|
||||
This is my attempt to rewrite [arjunmehta's multiview](https://github.com/arjunmehta/multiview) in rust.
|
||||
|
||||
Their version has many features that I don't use, but is missing a few things that I need:
|
||||
- line wrapping: when a line is bigger than the terminal size, the end is just not displayed
|
||||
- scroll: if your output has more lines than your terminal height, there is no way (to my knowledge) to scroll up
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo -en "0"
|
||||
for i in `seq 1 100`; do
|
||||
sleep 0.05s
|
||||
sleep 0.005s
|
||||
echo -en "\n$i"
|
||||
done
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
width=$(stty size | cut -d ' ' -f 2)
|
||||
|
||||
for c in a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9; do
|
||||
for i in `seq 1 $width`; do
|
||||
for i in `seq 1 $(stty size | cut -d ' ' -f 2)`; do
|
||||
echo -n $c
|
||||
done
|
||||
sleep 2s
|
||||
done
|
||||
|
||||
BIN
screenshots/col-major.png
Normal file
BIN
screenshots/col-major.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
BIN
screenshots/row-major.png
Normal file
BIN
screenshots/row-major.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
246
src/lib.rs
246
src/lib.rs
@@ -10,10 +10,12 @@ use termion::screen::IntoAlternateScreen;
|
||||
use termion::terminal_size;
|
||||
use termion::{clear, cursor};
|
||||
|
||||
use tile::{Tile, TileBuilder};
|
||||
|
||||
pub mod tile;
|
||||
pub mod utils;
|
||||
|
||||
use tile::{Tile, TileBuilder};
|
||||
const DELAY: Duration = Duration::from_millis(20);
|
||||
|
||||
/// Multiple applications running in a single terminal.
|
||||
struct Multiview<W: Write> {
|
||||
@@ -29,6 +31,9 @@ struct Multiview<W: Write> {
|
||||
/// Whether we need to refresh the UI.
|
||||
pub refresh_ui: bool,
|
||||
|
||||
/// Whether we need to refresh the tiles.
|
||||
pub refresh_tiles: bool,
|
||||
|
||||
/// Last time when the rendering was performed.
|
||||
pub last_render: Instant,
|
||||
}
|
||||
@@ -41,6 +46,7 @@ impl<W: Write> Multiview<W> {
|
||||
tiles,
|
||||
selected: (0, 0),
|
||||
refresh_ui: true,
|
||||
refresh_tiles: false,
|
||||
last_render: Instant::now(),
|
||||
};
|
||||
|
||||
@@ -68,11 +74,17 @@ impl<W: Write> Multiview<W> {
|
||||
}
|
||||
|
||||
/// Sets the selected tile from (x, y) coordinates.
|
||||
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 h = term_size.1 / self.tiles.len() as u16;
|
||||
|
||||
self.selected = (y / h, x / w);
|
||||
pub fn select_tile(&mut self, (x, y): (u16, u16)) {
|
||||
// Ugly but working
|
||||
for (i, row) in self.tiles.iter().enumerate() {
|
||||
for (j, tile) in row.iter().enumerate() {
|
||||
if tile.outer_position.0 <= x && x < tile.outer_position.0 + tile.outer_size.0 {
|
||||
if tile.outer_position.1 <= y && y < tile.outer_position.1 + tile.outer_size.1 {
|
||||
self.selected = (i as u16, j as u16);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.refresh_ui = true;
|
||||
}
|
||||
|
||||
@@ -85,21 +97,31 @@ impl<W: Write> Multiview<W> {
|
||||
/// Renders the (x, y) tile.
|
||||
pub fn render_tile_content(&mut self, (i, j): (u16, u16)) -> String {
|
||||
let tile = self.tile((i, j));
|
||||
tile.render_content()
|
||||
tile.render_content(self.selected == (i, j))
|
||||
}
|
||||
|
||||
/// Renders all the tiles of the multiview.
|
||||
pub fn render(&mut self) -> io::Result<()> {
|
||||
// let now = Instant::now();
|
||||
pub fn render(&mut self, force: bool) -> io::Result<()> {
|
||||
if !self.refresh_tiles {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// if now.duration_since(self.last_render) < Duration::from_millis(20) {
|
||||
// return Ok(());
|
||||
// }
|
||||
let now = Instant::now();
|
||||
|
||||
if now.duration_since(self.last_render) < DELAY && !force {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.last_render = now;
|
||||
|
||||
let mut buffer = if self.refresh_ui {
|
||||
vec![format!("{}", clear::All)]
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
// self.last_render = now;
|
||||
let mut buffer = vec![];
|
||||
for i in 0..self.tiles.len() {
|
||||
for j in 0..self.tiles[0].len() {
|
||||
for j in 0..self.tiles[i].len() {
|
||||
if self.refresh_ui {
|
||||
buffer.push(self.render_tile_border((i as u16, j as u16)));
|
||||
}
|
||||
@@ -108,6 +130,7 @@ impl<W: Write> Multiview<W> {
|
||||
}
|
||||
|
||||
self.refresh_ui = false;
|
||||
self.refresh_tiles = false;
|
||||
write!(self.stdout, "{}", buffer.join(""))?;
|
||||
self.stdout.flush()?;
|
||||
|
||||
@@ -115,15 +138,15 @@ impl<W: Write> Multiview<W> {
|
||||
}
|
||||
|
||||
/// Scrolls down the current selected tile.
|
||||
pub fn scroll_down(&mut self) {
|
||||
pub fn scroll_down(&mut self, step: isize) {
|
||||
let tile = self.tile_mut(self.selected);
|
||||
tile.scroll_down();
|
||||
tile.scroll_down(step);
|
||||
}
|
||||
|
||||
/// Scrolls up the current selected tile.
|
||||
pub fn scroll_up(&mut self) {
|
||||
pub fn scroll_up(&mut self, step: isize) {
|
||||
let tile = self.tile_mut(self.selected);
|
||||
tile.scroll_up();
|
||||
tile.scroll_up(step);
|
||||
}
|
||||
|
||||
/// Scrolls down to the bottom of the current selected tile.
|
||||
@@ -150,37 +173,54 @@ impl<W: Write> Multiview<W> {
|
||||
}
|
||||
|
||||
/// Restarts the selected tile.
|
||||
pub fn restart(&mut self) -> io::Result<()> {
|
||||
pub fn restart(&mut self) {
|
||||
let tile = self.tile_mut(self.selected);
|
||||
tile.restart()
|
||||
tile.restart();
|
||||
}
|
||||
|
||||
/// Restarts all tiles.
|
||||
pub fn restart_all(&mut self) -> io::Result<()> {
|
||||
pub fn restart_all(&mut self) {
|
||||
for row in &mut self.tiles {
|
||||
for tile in row {
|
||||
tile.restart()?;
|
||||
tile.restart();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Kills the selected tile.
|
||||
pub fn kill(&mut self) -> io::Result<()> {
|
||||
pub fn kill(&mut self) {
|
||||
let tile = self.tile_mut(self.selected);
|
||||
tile.kill()
|
||||
tile.kill();
|
||||
}
|
||||
|
||||
/// Kills all tiles.
|
||||
pub fn kill_all(&mut self) -> io::Result<()> {
|
||||
pub fn kill_all(&mut self) {
|
||||
for row in &mut self.tiles {
|
||||
for tile in row {
|
||||
tile.kill()?;
|
||||
tile.kill();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
/// Adds a line to the current tile.
|
||||
pub fn add_line(&mut self) {
|
||||
let tile = self.tile_mut(self.selected);
|
||||
tile.add_line();
|
||||
}
|
||||
|
||||
/// Adds a line to every tile.
|
||||
pub fn add_line_all(&mut self) {
|
||||
for row in &mut self.tiles {
|
||||
for tile in row {
|
||||
tile.add_line();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a finish line to the specified tile.
|
||||
pub fn add_finish_line(&mut self, coords: (u16, u16), success: bool) {
|
||||
let tile = self.tile_mut(coords);
|
||||
tile.add_finish_line(success);
|
||||
}
|
||||
|
||||
/// Exits.
|
||||
@@ -189,10 +229,35 @@ impl<W: Write> Multiview<W> {
|
||||
|
||||
for row in &mut self.tiles {
|
||||
for tile in row {
|
||||
tile.kill().ok();
|
||||
tile.kill()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Treats a message.
|
||||
pub fn manage_msg(&mut self, msg: Msg) -> io::Result<()> {
|
||||
self.refresh_tiles = true;
|
||||
|
||||
match msg {
|
||||
Msg::Stdout(coords, line) => self.push_stdout(coords, line),
|
||||
Msg::Stderr(coords, line) => self.push_stderr(coords, line),
|
||||
Msg::Click(x, y) => self.select_tile((x, y)),
|
||||
Msg::Restart => self.restart(),
|
||||
Msg::RestartAll => self.restart_all(),
|
||||
Msg::Kill => self.kill(),
|
||||
Msg::KillAll => self.kill_all(),
|
||||
Msg::ScrollDown(step) => self.scroll_down(step),
|
||||
Msg::ScrollUp(step) => self.scroll_up(step),
|
||||
Msg::ScrollFullDown => self.scroll_full_down(),
|
||||
Msg::ScrollFullUp => self.scroll_full_up(),
|
||||
Msg::AddLine => self.add_line(),
|
||||
Msg::AddLineAll => self.add_line_all(),
|
||||
Msg::AddFinishLine(coords, success) => self.add_finish_line(coords, success),
|
||||
Msg::Exit => self.exit(),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> Drop for Multiview<W> {
|
||||
@@ -202,6 +267,7 @@ impl<W: Write> Drop for Multiview<W> {
|
||||
}
|
||||
|
||||
/// An event that can be sent in channels.
|
||||
#[derive(PartialEq, Eq)]
|
||||
pub enum Msg {
|
||||
/// An stdout line arrived.
|
||||
Stdout((u16, u16), String),
|
||||
@@ -225,10 +291,10 @@ pub enum Msg {
|
||||
KillAll,
|
||||
|
||||
/// Scroll up one line.
|
||||
ScrollUp,
|
||||
ScrollUp(isize),
|
||||
|
||||
/// Scroll down one line.
|
||||
ScrollDown,
|
||||
ScrollDown(isize),
|
||||
|
||||
/// Scroll to the top of the log.
|
||||
ScrollFullUp,
|
||||
@@ -236,6 +302,15 @@ pub enum Msg {
|
||||
/// Scroll to the bottom of the log.
|
||||
ScrollFullDown,
|
||||
|
||||
/// Adds a line to the current tile.
|
||||
AddLine,
|
||||
|
||||
/// Adds a line to every tile.
|
||||
AddLineAll,
|
||||
|
||||
/// Adds the finish line to the tile.
|
||||
AddFinishLine((u16, u16), bool),
|
||||
|
||||
/// The program was asked to exit.
|
||||
Exit,
|
||||
}
|
||||
@@ -246,9 +321,33 @@ pub fn main() -> io::Result<()> {
|
||||
|
||||
let args = env::args().skip(1).collect::<Vec<_>>();
|
||||
|
||||
let mut is_row_major = true;
|
||||
|
||||
for arg in &args {
|
||||
if arg == "//" {
|
||||
is_row_major = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if arg == "::" {
|
||||
is_row_major = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let (first_split, second_split) = if is_row_major {
|
||||
("//", "::")
|
||||
} else {
|
||||
("::", "//")
|
||||
};
|
||||
|
||||
let tiles = args
|
||||
.split(|x| x == "//")
|
||||
.map(|x| x.split(|y| y == "::").enumerate().collect::<Vec<_>>())
|
||||
.split(|x| x == first_split)
|
||||
.map(|x| {
|
||||
x.split(|y| y == second_split)
|
||||
.enumerate()
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.enumerate()
|
||||
.map(|(i, tiles)| {
|
||||
tiles
|
||||
@@ -258,22 +357,29 @@ pub fn main() -> io::Result<()> {
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let term_size = terminal_size()?;
|
||||
let mut term_size = terminal_size()?;
|
||||
|
||||
let tile_size = (
|
||||
term_size.0 / tiles[0].len() as u16,
|
||||
term_size.1 / tiles.len() as u16,
|
||||
);
|
||||
let col_len = tiles.len() as u16;
|
||||
|
||||
let tiles = tiles
|
||||
.into_iter()
|
||||
.map(|row| {
|
||||
let row_len = row.len() as u16;
|
||||
|
||||
let tile_size = if is_row_major {
|
||||
(term_size.0 / row_len, term_size.1 / col_len)
|
||||
} else {
|
||||
(term_size.0 / col_len, term_size.1 / row_len)
|
||||
};
|
||||
|
||||
row.into_iter()
|
||||
.map(|((i, j), tile)| {
|
||||
let (p_i, p_j) = if is_row_major { (i, j) } else { (j, i) };
|
||||
|
||||
TileBuilder::new()
|
||||
.command(tile.into())
|
||||
.coords((i as u16, j as u16))
|
||||
.position((j as u16 * tile_size.0 + 1, i as u16 * tile_size.1 + 1))
|
||||
.position((p_j as u16 * tile_size.0 + 1, p_i as u16 * tile_size.1 + 1))
|
||||
.size(tile_size)
|
||||
.sender(sender.clone())
|
||||
.build()
|
||||
@@ -289,7 +395,7 @@ pub fn main() -> io::Result<()> {
|
||||
let stdout = MouseTerminal::from(stdout);
|
||||
|
||||
let mut multiview = Multiview::new(stdout, tiles)?;
|
||||
multiview.render()?;
|
||||
multiview.render(true)?;
|
||||
|
||||
for row in &mut multiview.tiles {
|
||||
for tile in row {
|
||||
@@ -306,13 +412,15 @@ pub fn main() -> io::Result<()> {
|
||||
Event::Key(Key::Char('R')) => sender.send(Msg::RestartAll).unwrap(),
|
||||
Event::Key(Key::Char('k')) => sender.send(Msg::Kill).unwrap(),
|
||||
Event::Key(Key::Char('K')) => sender.send(Msg::KillAll).unwrap(),
|
||||
Event::Key(Key::Down) => sender.send(Msg::ScrollDown).unwrap(),
|
||||
Event::Key(Key::Up) => sender.send(Msg::ScrollUp).unwrap(),
|
||||
Event::Key(Key::Char('l')) => sender.send(Msg::AddLine).unwrap(),
|
||||
Event::Key(Key::Char('L')) => sender.send(Msg::AddLineAll).unwrap(),
|
||||
Event::Key(Key::Down) => sender.send(Msg::ScrollDown(1)).unwrap(),
|
||||
Event::Key(Key::Up) => sender.send(Msg::ScrollUp(1)).unwrap(),
|
||||
Event::Key(Key::End) => sender.send(Msg::ScrollFullDown).unwrap(),
|
||||
Event::Key(Key::Home) => sender.send(Msg::ScrollFullUp).unwrap(),
|
||||
Event::Mouse(MouseEvent::Press(p, x, y)) => match p {
|
||||
MouseButton::WheelUp => sender.send(Msg::ScrollUp).unwrap(),
|
||||
MouseButton::WheelDown => sender.send(Msg::ScrollDown).unwrap(),
|
||||
MouseButton::WheelUp => sender.send(Msg::ScrollUp(3)).unwrap(),
|
||||
MouseButton::WheelDown => sender.send(Msg::ScrollDown(3)).unwrap(),
|
||||
MouseButton::Left => sender.send(Msg::Click(x, y)).unwrap(),
|
||||
_ => (),
|
||||
},
|
||||
@@ -323,26 +431,40 @@ pub fn main() -> io::Result<()> {
|
||||
});
|
||||
|
||||
loop {
|
||||
match receiver.recv() {
|
||||
Ok(Msg::Stdout(coords, line)) => multiview.push_stdout(coords, line),
|
||||
Ok(Msg::Stderr(coords, line)) => multiview.push_stderr(coords, line),
|
||||
Ok(Msg::Click(x, y)) => multiview.select_tile((x, y), term_size),
|
||||
Ok(Msg::ScrollDown) => multiview.scroll_down(),
|
||||
Ok(Msg::Restart) => multiview.restart()?,
|
||||
Ok(Msg::RestartAll) => multiview.restart_all()?,
|
||||
Ok(Msg::Kill) => multiview.kill()?,
|
||||
Ok(Msg::KillAll) => multiview.kill_all()?,
|
||||
Ok(Msg::ScrollUp) => multiview.scroll_up(),
|
||||
Ok(Msg::ScrollFullDown) => multiview.scroll_full_down(),
|
||||
Ok(Msg::ScrollFullUp) => multiview.scroll_full_up(),
|
||||
Ok(Msg::Exit) => {
|
||||
multiview.exit();
|
||||
if let Ok(msg) = receiver.recv_timeout(DELAY) {
|
||||
let is_exit = msg == Msg::Exit;
|
||||
multiview.manage_msg(msg)?;
|
||||
if is_exit {
|
||||
break;
|
||||
}
|
||||
Err(_) => (),
|
||||
}
|
||||
|
||||
multiview.render()?;
|
||||
let new_term_size = terminal_size()?;
|
||||
|
||||
if term_size != new_term_size {
|
||||
term_size = new_term_size;
|
||||
|
||||
for (i, row) in multiview.tiles.iter_mut().enumerate() {
|
||||
let row_len = row.len() as u16;
|
||||
|
||||
let tile_size = if is_row_major {
|
||||
(term_size.0 / row_len, term_size.1 / col_len)
|
||||
} else {
|
||||
(term_size.0 / col_len, term_size.1 / row_len)
|
||||
};
|
||||
|
||||
for (j, tile) in row.iter_mut().enumerate() {
|
||||
let (p_i, p_j) = if is_row_major { (i, j) } else { (j, i) };
|
||||
tile.reposition((p_j as u16 * tile_size.0 + 1, p_i as u16 * tile_size.1 + 1));
|
||||
tile.resize(tile_size);
|
||||
}
|
||||
}
|
||||
|
||||
multiview.refresh_tiles = true;
|
||||
multiview.refresh_ui = true;
|
||||
}
|
||||
|
||||
multiview.render(false)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
278
src/tile.rs
278
src/tile.rs
@@ -1,7 +1,7 @@
|
||||
//! This module contains everything related to tiles.
|
||||
|
||||
use std::io::{self, Read};
|
||||
use std::process::{Child, Stdio};
|
||||
use std::io::Read;
|
||||
use std::process::Stdio;
|
||||
use std::sync::mpsc::Sender;
|
||||
use std::thread;
|
||||
|
||||
@@ -96,7 +96,8 @@ impl TileBuilder {
|
||||
scroll: 0,
|
||||
counting: true,
|
||||
column_number: 0,
|
||||
child: None,
|
||||
pty: None,
|
||||
sticky: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -140,8 +141,11 @@ pub struct Tile {
|
||||
/// The number of the current column.
|
||||
pub column_number: u16,
|
||||
|
||||
/// The PTY and the child process of the command running in the tile.
|
||||
pub child: Option<(Pty, Child)>,
|
||||
/// The PTY of the command running in the tile.
|
||||
pub pty: Option<Pty>,
|
||||
|
||||
/// Whether the tile should autoscroll.
|
||||
pub sticky: bool,
|
||||
}
|
||||
|
||||
impl Tile {
|
||||
@@ -161,12 +165,44 @@ impl Tile {
|
||||
let pty = Pty::new().unwrap();
|
||||
pty.resize(pty_process::Size::new(size.1, size.0)).unwrap();
|
||||
|
||||
let mut child = Command::new(&clone[0])
|
||||
let child = Command::new(&clone[0])
|
||||
.args(&clone[1..])
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn(&pty.pts().unwrap())
|
||||
.unwrap();
|
||||
.spawn(&pty.pts().unwrap());
|
||||
|
||||
let mut child = match child {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
let exit_string = format!(
|
||||
"{}{}Couldn't run command: {}\r{}",
|
||||
style::Bold,
|
||||
color::Red.fg_str(),
|
||||
e,
|
||||
style::Reset,
|
||||
);
|
||||
|
||||
sender.send(Msg::Stdout(coords, exit_string)).unwrap();
|
||||
|
||||
let mut line = String::new();
|
||||
for _ in 0..size.0 - 1 {
|
||||
line.push('─');
|
||||
}
|
||||
|
||||
sender
|
||||
.send(Msg::Stdout(
|
||||
coords,
|
||||
format!(
|
||||
"\n{}{}{}\n",
|
||||
color::Red.fg_str(),
|
||||
line,
|
||||
color::Reset.fg_str()
|
||||
),
|
||||
))
|
||||
.unwrap();
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut stdout = child.stdout.take().unwrap();
|
||||
let mut stderr = child.stderr.take().unwrap();
|
||||
@@ -195,33 +231,41 @@ impl Tile {
|
||||
}
|
||||
}
|
||||
|
||||
let code = child.wait().unwrap().code();
|
||||
|
||||
sender
|
||||
.send(Msg::Stdout(coords, String::from("\n")))
|
||||
.unwrap();
|
||||
|
||||
let code = 0;
|
||||
|
||||
let exit_string = format!(
|
||||
"{}{}Command exited with return code {}\r{}",
|
||||
style::Bold,
|
||||
if code == 0 {
|
||||
color::Green.fg_str()
|
||||
} else {
|
||||
color::Red.fg_str()
|
||||
},
|
||||
code,
|
||||
style::Reset,
|
||||
);
|
||||
let exit_string = match code {
|
||||
Some(0) => format!(
|
||||
"{}{}Command finished successfully\r{}",
|
||||
style::Bold,
|
||||
color::Green.fg_str(),
|
||||
style::Reset,
|
||||
),
|
||||
Some(x) => {
|
||||
format!(
|
||||
"{}{}Command failed with exit code {}\r{}",
|
||||
style::Bold,
|
||||
color::Red.fg_str(),
|
||||
x,
|
||||
style::Reset,
|
||||
)
|
||||
}
|
||||
None => {
|
||||
format!(
|
||||
"{}{}Command was interrupted\r{}",
|
||||
style::Bold,
|
||||
color::Red.fg_str(),
|
||||
style::Reset,
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
sender.send(Msg::Stdout(coords, exit_string)).unwrap();
|
||||
|
||||
let mut line = String::new();
|
||||
for _ in 0..size.0 - 1 {
|
||||
line.push('─');
|
||||
}
|
||||
|
||||
sender
|
||||
.send(Msg::Stdout(coords, format!("\n{}\n", line)))
|
||||
.send(Msg::AddFinishLine(coords, code == Some(0)))
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
@@ -245,7 +289,7 @@ impl Tile {
|
||||
}
|
||||
});
|
||||
|
||||
self.child = Some((pty, child));
|
||||
self.pty = Some(pty);
|
||||
}
|
||||
|
||||
/// Push content into the stdout of the tile.
|
||||
@@ -275,7 +319,7 @@ impl Tile {
|
||||
|
||||
if self.counting && !is_variation_selector {
|
||||
self.column_number += 1;
|
||||
if self.column_number == self.inner_size.0 + 1 {
|
||||
if self.column_number == self.inner_size.0 {
|
||||
self.stdout.push(String::new());
|
||||
self.column_number = 0;
|
||||
}
|
||||
@@ -289,9 +333,8 @@ impl Tile {
|
||||
}
|
||||
|
||||
// Autoscroll whene content arrives on stdout
|
||||
self.scroll = self.stdout.len() as isize - 1 - (self.inner_size.1 as isize);
|
||||
if self.scroll < 0 {
|
||||
self.scroll = 0;
|
||||
if self.sticky {
|
||||
self.scroll = self.max_scroll();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -346,7 +389,7 @@ impl Tile {
|
||||
}
|
||||
|
||||
/// Renders the content of the tile.
|
||||
pub fn render_content(&self) -> String {
|
||||
pub fn render_content(&self, selected: bool) -> String {
|
||||
const DELETE_CHAR: char = ' ';
|
||||
|
||||
let (x, y) = self.inner_position;
|
||||
@@ -448,8 +491,6 @@ impl Tile {
|
||||
}
|
||||
buffer.push(spaces);
|
||||
|
||||
eprintln!("Clear {}", max_char_index);
|
||||
|
||||
line_index += 1;
|
||||
current_char_index = 0;
|
||||
max_char_index = 0;
|
||||
@@ -499,60 +540,171 @@ impl Tile {
|
||||
}
|
||||
}
|
||||
|
||||
let mut spaces = format!(
|
||||
"{}",
|
||||
cursor::Goto(x + max_char_index, y + last_line_index as u16 - scroll)
|
||||
);
|
||||
if last_line_index as u16 - scroll <= h {
|
||||
let mut spaces = format!(
|
||||
"{}",
|
||||
cursor::Goto(x + max_char_index, y + last_line_index as u16 - scroll)
|
||||
);
|
||||
|
||||
for _ in max_char_index..w {
|
||||
spaces.push(DELETE_CHAR);
|
||||
for _ in max_char_index..w {
|
||||
spaces.push(DELETE_CHAR);
|
||||
}
|
||||
buffer.push(spaces);
|
||||
}
|
||||
|
||||
// Render scrollbar,thanks @gdamms
|
||||
// I have no idea what this code does, I copied/pasted it from gdamms, and then modified
|
||||
// some stuff so that it would look right
|
||||
if last_line_index > h {
|
||||
let mut subbuffer = vec![];
|
||||
subbuffer.push(format!(
|
||||
"{}{}{}{}",
|
||||
style::Reset,
|
||||
if selected { color::Green.fg_str() } else { "" },
|
||||
cursor::Goto(x + w + 1, y),
|
||||
"▲"
|
||||
));
|
||||
|
||||
let bar_portion = h as f32 / self.stdout.len() as f32;
|
||||
let bar_nb = f32::max(1.0, (bar_portion * (h) as f32).round()) as u16;
|
||||
let max_scroll = self.stdout.len() as isize - h as isize - 1;
|
||||
|
||||
let (scroll_nb_bottom, scroll_nb_top) = if self.scroll > max_scroll / 2 {
|
||||
let scroll_nb_bottom = (self.stdout.len() as isize - self.scroll) as u16 - h;
|
||||
let scroll_nb_bottom = scroll_nb_bottom as f32 / self.stdout.len() as f32;
|
||||
let scroll_nb_bottom = (scroll_nb_bottom * (h as f32)).ceil() as u16;
|
||||
let scroll_nb_top = h - bar_nb - scroll_nb_bottom;
|
||||
(scroll_nb_bottom, scroll_nb_top)
|
||||
} else {
|
||||
let scroll_nb_top = self.scroll as f32 / self.stdout.len() as f32;
|
||||
let scroll_nb_top = (scroll_nb_top * (h) as f32).ceil() as u16;
|
||||
let scroll_nb_bottom = h - bar_nb - scroll_nb_top;
|
||||
(scroll_nb_bottom, scroll_nb_top)
|
||||
};
|
||||
|
||||
for i in 1..=scroll_nb_top {
|
||||
subbuffer.push(format!("{}{}", cursor::Goto(x + w + 1, y + i), "│"));
|
||||
}
|
||||
for i in scroll_nb_top + 1..=scroll_nb_top + bar_nb {
|
||||
subbuffer.push(format!("{}{}", cursor::Goto(x + w + 1, y + i), "█"));
|
||||
}
|
||||
for i in scroll_nb_top + bar_nb + 1..=scroll_nb_top + bar_nb + scroll_nb_bottom {
|
||||
subbuffer.push(format!("{}{}", cursor::Goto(x + w + 1, y + i), "│"));
|
||||
}
|
||||
|
||||
subbuffer.push(format!("{}{}", cursor::Goto(x + w + 1, y + h), "▼"));
|
||||
|
||||
buffer.push(subbuffer.join(""));
|
||||
}
|
||||
buffer.push(spaces);
|
||||
|
||||
buffer.push(format!("{}", style::Reset));
|
||||
buffer.join("")
|
||||
}
|
||||
|
||||
/// Returns the max scroll value.
|
||||
pub fn max_scroll(&self) -> isize {
|
||||
std::cmp::max(
|
||||
0,
|
||||
self.stdout.len() as isize - self.inner_size.1 as isize - 1,
|
||||
)
|
||||
}
|
||||
|
||||
/// Scrolls up one line.
|
||||
pub fn scroll_up(&mut self) {
|
||||
if self.scroll > 0 {
|
||||
self.scroll -= 1;
|
||||
}
|
||||
pub fn scroll_up(&mut self, step: isize) {
|
||||
self.sticky = false;
|
||||
self.scroll = std::cmp::max(0, self.scroll - step);
|
||||
}
|
||||
|
||||
/// Scrolls down one line.
|
||||
pub fn scroll_down(&mut self) {
|
||||
if self.scroll + (self.inner_size.1 as isize) < self.stdout.len() as isize - 1 {
|
||||
self.scroll += 1;
|
||||
pub fn scroll_down(&mut self, step: isize) {
|
||||
let max_scroll = self.max_scroll();
|
||||
self.scroll = std::cmp::min(max_scroll, self.scroll + step);
|
||||
if self.scroll == max_scroll {
|
||||
self.sticky = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Scrolls up one line.
|
||||
pub fn scroll_full_up(&mut self) {
|
||||
self.sticky = false;
|
||||
self.scroll = 0;
|
||||
}
|
||||
|
||||
/// Scrolls down one line.
|
||||
pub fn scroll_full_down(&mut self) {
|
||||
self.scroll = self.stdout.len() as isize - self.inner_size.1 as isize - 1;
|
||||
if self.scroll < 0 {
|
||||
self.scroll = 0;
|
||||
}
|
||||
self.sticky = true;
|
||||
self.scroll = self.max_scroll()
|
||||
}
|
||||
|
||||
/// Kill the child command.
|
||||
pub fn kill(&mut self) -> io::Result<()> {
|
||||
if let Some((_, child)) = self.child.as_mut() {
|
||||
child.kill()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
pub fn kill(&mut self) {
|
||||
self.pty = None;
|
||||
}
|
||||
|
||||
/// Restarts the child command.
|
||||
pub fn restart(&mut self) -> io::Result<()> {
|
||||
self.kill()?;
|
||||
pub fn restart(&mut self) {
|
||||
self.kill();
|
||||
self.start();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Repositions the tile.
|
||||
pub fn reposition(&mut self, (i, j): (u16, u16)) {
|
||||
self.outer_position = (i, j);
|
||||
self.inner_position = (i + 2, j + 3);
|
||||
}
|
||||
|
||||
/// Resizes the tile.
|
||||
pub fn resize(&mut self, (w, h): (u16, u16)) {
|
||||
self.outer_size = (w, h);
|
||||
self.inner_size = (w - 4, h - 5);
|
||||
|
||||
if let Some(pty) = self.pty.as_mut() {
|
||||
pty.resize(pty_process::Size::new(self.inner_size.1, self.inner_size.0))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let old_stdout = std::mem::replace(&mut self.stdout, vec![String::new()]);
|
||||
for s in old_stdout {
|
||||
self.push_stdout(s);
|
||||
}
|
||||
}
|
||||
|
||||
/// Draws a line.
|
||||
pub fn add_line(&mut self) {
|
||||
let mut line = String::new();
|
||||
for _ in 0..self.inner_size.0 - 1 {
|
||||
line.push('─');
|
||||
}
|
||||
|
||||
self.sender
|
||||
.send(Msg::Stdout(
|
||||
self.coords,
|
||||
format!("\n{}{}\n", color::Reset.fg_str(), line),
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Draws a finish line, green if success or red if failure.
|
||||
pub fn add_finish_line(&mut self, success: bool) {
|
||||
let mut line = String::new();
|
||||
for _ in 0..self.inner_size.0 - 1 {
|
||||
line.push('─');
|
||||
}
|
||||
|
||||
self.sender
|
||||
.send(Msg::Stdout(
|
||||
self.coords,
|
||||
format!(
|
||||
"\n{}{}{}\n",
|
||||
color::Reset.fg_str(),
|
||||
if success {
|
||||
color::Green.fg_str()
|
||||
} else {
|
||||
color::Red.fg_str()
|
||||
},
|
||||
line
|
||||
),
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user