From e7fa88dcd4b261af643a9aedfce0f0a1d9a97462 Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Thu, 11 Nov 2021 15:19:31 -0500 Subject: handle dynamic terminal sizes --- Cargo.lock | 15 ++++++++++++++ Cargo.toml | 3 +++ src/action.rs | 19 ++++++++++++----- src/history.rs | 64 +++++++++++++++++++++++++++++++++++++++++++++++++-------- src/main.rs | 17 +++++++++++++++ src/readline.rs | 8 +++++++- src/state.rs | 26 ++++++++++++++++++++--- 7 files changed, 135 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a08ee94..1346c87 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -381,6 +381,9 @@ dependencies = [ "libc", "nix", "pty-process", + "signal-hook", + "signal-hook-async-std", + "terminal_size", "textmode", "vt100", ] @@ -486,6 +489,18 @@ dependencies = [ "signal-hook-registry", ] +[[package]] +name = "signal-hook-async-std" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90526e74631c69a79b38212e3d4fda4b00de9d6be56b3cead133bf67ad371af1" +dependencies = [ + "async-io", + "futures-lite", + "libc", + "signal-hook", +] + [[package]] name = "signal-hook-registry" version = "1.4.0" diff --git a/Cargo.toml b/Cargo.toml index 4328f71..3fe3332 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,9 @@ futures-lite = "1.12.0" libc = "0.2.107" nix = "0.23.0" pty-process = { version = "0.1.1", features = ["backend-async-std"] } +signal-hook = "0.3.10" +signal-hook-async-std = "0.2.1" +terminal_size = "0.1.17" textmode = { version = "0.1.1", features = ["async"] } vt100 = "0.12.0" diff --git a/src/action.rs b/src/action.rs index 2cd54b7..81f83bb 100644 --- a/src/action.rs +++ b/src/action.rs @@ -3,6 +3,7 @@ pub enum Action { Render, Run(String), UpdateFocus(crate::state::Focus), + Resize((u16, u16)), } pub struct Debouncer { @@ -30,9 +31,10 @@ impl Debouncer { #[derive(Default)] struct Pending { - render: bool, + render: Option<()>, run: std::collections::VecDeque, focus: Option, + size: Option<(u16, u16)>, done: bool, } @@ -42,18 +44,24 @@ impl Pending { } fn has_event(&self) -> bool { - self.render || !self.run.is_empty() || self.focus.is_some() + self.render.is_some() + || !self.run.is_empty() + || self.focus.is_some() + || self.size.is_some() } fn get_event(&mut self) -> Option { + if self.size.is_some() { + return Some(Action::Resize(self.size.take().unwrap())); + } if !self.run.is_empty() { return Some(Action::Run(self.run.pop_front().unwrap())); } if self.focus.is_some() { return Some(Action::UpdateFocus(self.focus.take().unwrap())); } - if self.render { - self.render = false; + if self.render.is_some() { + self.render.take(); return Some(Action::Render); } if self.done { @@ -66,7 +74,8 @@ impl Pending { match action { Some(Action::Run(cmd)) => self.run.push_back(cmd.to_string()), Some(Action::UpdateFocus(focus)) => self.focus = Some(*focus), - Some(Action::Render) => self.render = true, + Some(Action::Render) => self.render = Some(()), + Some(Action::Resize(size)) => self.size = Some(*size), None => self.done = true, } } diff --git a/src/history.rs b/src/history.rs index 348224b..b0452cb 100644 --- a/src/history.rs +++ b/src/history.rs @@ -1,8 +1,10 @@ use async_std::io::{ReadExt as _, WriteExt as _}; +use futures_lite::future::FutureExt as _; use pty_process::Command as _; use textmode::Textmode as _; pub struct History { + size: (u16, u16), entries: Vec>, action: async_std::channel::Sender, } @@ -12,6 +14,7 @@ impl History { action: async_std::channel::Sender, ) -> Self { Self { + size: (24, 80), entries: vec![], action, } @@ -22,13 +25,19 @@ impl History { let mut process = async_std::process::Command::new(&exe); process.args(&args); let child = process - .spawn_pty(Some(&pty_process::Size::new(24, 80))) + .spawn_pty(Some(&pty_process::Size::new( + self.size.0, + self.size.1, + ))) .unwrap(); let (input_w, input_r) = async_std::channel::unbounded(); + let (resize_w, resize_r) = async_std::channel::unbounded(); let entry = crate::util::mutex(HistoryEntry::new( cmd, child.id().try_into().unwrap(), + self.size, input_w, + resize_w, )); let task_entry = async_std::sync::Arc::clone(&entry); let task_action = self.action.clone(); @@ -37,12 +46,14 @@ impl History { enum Res { Read(Result), Write(Result, async_std::channel::RecvError>), + Resize(Result<(u16, u16), async_std::channel::RecvError>), } let mut buf = [0_u8; 4096]; let mut pty = child.pty(); let read = async { Res::Read(pty.read(&mut buf).await) }; let write = async { Res::Write(input_r.recv().await) }; - match futures_lite::future::race(read, write).await { + let resize = async { Res::Resize(resize_r.recv().await) }; + match read.race(write).race(resize).await { Res::Read(res) => { match res { Ok(bytes) => { @@ -82,6 +93,26 @@ impl History { ); } }, + Res::Resize(res) => match res { + Ok(size) => { + child + .resize_pty(&pty_process::Size::new( + size.0, size.1, + )) + .unwrap(); + task_entry + .lock_arc() + .await + .vt + .set_size(size.0, size.1); + } + Err(e) => { + panic!( + "failed to read from resize channel: {}", + e + ); + } + }, } } }); @@ -135,7 +166,7 @@ impl History { 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() { + for (idx, row) in screen.rows(0, self.size.1).enumerate() { if !row.is_empty() { last_row = idx + 1; } @@ -147,14 +178,17 @@ impl History { ); } used_lines += 1 + std::cmp::min(6, last_row); - if used_lines > 24 { + if used_lines > self.size.0 as usize { break; } if used_lines == 1 { used_lines = 2; - pos = Some((23, 0)); + pos = Some((self.size.0 - 1, 0)); } - out.move_to((24 - used_lines).try_into().unwrap(), 0); + out.move_to( + (self.size.0 as usize - used_lines).try_into().unwrap(), + 0, + ); out.write_str("$ "); if entry.running { out.set_bgcolor(vt100::Color::Rgb(16, 64, 16)); @@ -169,7 +203,7 @@ impl History { } let mut end_pos = (0, 0); for row in screen - .rows_formatted(0, 80) + .rows_formatted(0, self.size.1) .take(last_row) .skip(last_row.saturating_sub(5)) { @@ -188,6 +222,16 @@ impl History { Ok(()) } + pub async fn resize(&mut self, size: (u16, u16)) { + self.size = size; + for entry in &self.entries { + let entry = entry.lock_arc().await; + if entry.running { + entry.resize.send(size).await.unwrap(); + } + } + } + async fn send_process_input( &self, idx: usize, @@ -209,6 +253,7 @@ struct HistoryEntry { pid: nix::unistd::Pid, vt: vt100::Parser, input: async_std::channel::Sender>, + resize: async_std::channel::Sender<(u16, u16)>, running: bool, // option end time // start time } @@ -217,13 +262,16 @@ impl HistoryEntry { fn new( cmd: &str, pid: i32, + size: (u16, u16), input: async_std::channel::Sender>, + resize: async_std::channel::Sender<(u16, u16)>, ) -> Self { Self { cmd: cmd.into(), pid: nix::unistd::Pid::from_raw(pid), - vt: vt100::Parser::new(24, 80, 0), + vt: vt100::Parser::new(size.0, size.1, 0), input, + resize, running: true, } } diff --git a/src/main.rs b/src/main.rs index a0e4bd0..aa5fc35 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ #![warn(clippy::pedantic)] #![warn(clippy::nursery)] #![allow(clippy::missing_const_for_fn)] +#![allow(clippy::too_many_lines)] #![allow(clippy::unused_self)] mod action; @@ -9,6 +10,8 @@ mod readline; mod state; mod util; +use async_std::stream::StreamExt as _; + async fn async_main() -> anyhow::Result<()> { let mut input = textmode::Input::new().await?; let mut output = textmode::Output::new().await?; @@ -25,6 +28,20 @@ async fn async_main() -> anyhow::Result<()> { let state = util::mutex(state); + { + let state = async_std::sync::Arc::clone(&state); + let mut signals = signal_hook_async_std::Signals::new(&[ + signal_hook::consts::signal::SIGWINCH, + ])?; + async_std::task::spawn(async move { + while signals.next().await.is_some() { + state.lock_arc().await.resize().await; + } + }); + } + + state.lock_arc().await.resize().await; + { let state = async_std::sync::Arc::clone(&state); async_std::task::spawn(async move { diff --git a/src/readline.rs b/src/readline.rs index cc6c776..929905d 100644 --- a/src/readline.rs +++ b/src/readline.rs @@ -1,6 +1,7 @@ use textmode::Textmode as _; pub struct Readline { + size: (u16, u16), prompt: String, input_line: String, action: async_std::channel::Sender, @@ -11,6 +12,7 @@ impl Readline { action: async_std::channel::Sender, ) -> Self { Self { + size: (24, 80), prompt: "$ ".into(), input_line: "".into(), action, @@ -52,12 +54,16 @@ impl Readline { &self, out: &mut textmode::Output, ) -> anyhow::Result<()> { - out.move_to(23, 0); + out.move_to(self.size.0 - 1, 0); out.write_str(&self.prompt); out.write_str(&self.input_line); Ok(()) } + pub async fn resize(&mut self, size: (u16, u16)) { + self.size = size; + } + fn input(&self) -> String { self.input_line.clone() } diff --git a/src/state.rs b/src/state.rs index 854c306..2d5154a 100644 --- a/src/state.rs +++ b/src/state.rs @@ -5,21 +5,23 @@ pub struct State { history: crate::history::History, focus: Focus, output: textmode::Output, + action: async_std::channel::Sender, } impl State { pub fn new( - actions: async_std::channel::Sender, + action: async_std::channel::Sender, output: textmode::Output, ) -> Self { - let readline = crate::readline::Readline::new(actions.clone()); - let history = crate::history::History::new(actions); + let readline = crate::readline::Readline::new(action.clone()); + let history = crate::history::History::new(action.clone()); let focus = Focus::Readline; Self { readline, history, focus, output, + action, } } @@ -52,6 +54,13 @@ impl State { self.focus = new_focus; self.render().await.unwrap(); } + crate::action::Action::Resize(new_size) => { + self.readline.resize(new_size).await; + self.history.resize(new_size).await; + self.output.set_size(new_size.0, new_size.1); + self.output.clear(); + self.render().await.unwrap(); + } } } @@ -61,6 +70,17 @@ impl State { Focus::History(idx) => self.history.handle_key(key, idx).await, } } + + pub async fn resize(&mut self) { + let size = terminal_size::terminal_size().map_or( + (24, 80), + |(terminal_size::Width(w), terminal_size::Height(h))| (h, w), + ); + self.action + .send(crate::action::Action::Resize(size)) + .await + .unwrap(); + } } #[derive(Copy, Clone, Debug)] -- cgit v1.2.3-54-g00ecf