-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(tui): resize terminal pane (#8996)
### Description Resize the layout including the pane on terminal resize events. We completely sidestep `vt100`'s inability to properly handle resize events by storing a copy of task outputs and recreating the virtual terminal from scratch on a resize. A start at adding this functionality to vt100 can be found in [this commit](6def8c8). Generally speaking, this is inefficient, but there is a clear path forward if performance here becomes an issue. Each commit can be reviewed individually. ### Testing Instructions Added a few basic unit tests. Manual testing works best to verify terminal looks like how we want it to look. https://github.com/user-attachments/assets/661495d1-1a4a-4aa8-91de-cc0c2face05d
- Loading branch information
1 parent
1d4f77c
commit c4882fb
Showing
7 changed files
with
248 additions
and
45 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
use std::time::{Duration, Instant}; | ||
|
||
pub struct Debouncer<T> { | ||
value: Option<T>, | ||
duration: Duration, | ||
start: Option<Instant>, | ||
} | ||
|
||
impl<T> Debouncer<T> { | ||
/// Creates a new debouncer that will yield the latest value after the | ||
/// provided duration Duration is reset after the debouncer yields a | ||
/// value. | ||
pub fn new(duration: Duration) -> Self { | ||
Self { | ||
value: None, | ||
duration, | ||
start: None, | ||
} | ||
} | ||
|
||
/// Returns a value if debouncer duration has elapsed. | ||
#[must_use] | ||
pub fn query(&mut self) -> Option<T> { | ||
if self | ||
.start | ||
.map_or(false, |start| start.elapsed() >= self.duration) | ||
{ | ||
self.start = None; | ||
self.value.take() | ||
} else { | ||
None | ||
} | ||
} | ||
|
||
/// Updates debouncer with given value. Returns a value if debouncer | ||
/// duration has elapsed. | ||
#[must_use] | ||
pub fn update(&mut self, value: T) -> Option<T> { | ||
self.insert_value(Some(value)); | ||
self.query() | ||
} | ||
|
||
fn insert_value(&mut self, value: Option<T>) { | ||
// If there isn't a start set, bump it | ||
self.start.get_or_insert_with(Instant::now); | ||
if let Some(value) = value { | ||
self.value = Some(value); | ||
} | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use super::*; | ||
|
||
const DEFAULT_DURATION: Duration = Duration::from_millis(5); | ||
|
||
#[test] | ||
fn test_yields_after_duration() { | ||
let mut debouncer = Debouncer::new(DEFAULT_DURATION); | ||
assert!(debouncer.update(1).is_none()); | ||
assert!(debouncer.query().is_none()); | ||
std::thread::sleep(DEFAULT_DURATION); | ||
assert_eq!(debouncer.query(), Some(1)); | ||
assert!(debouncer.query().is_none()); | ||
} | ||
|
||
#[test] | ||
fn test_yields_latest() { | ||
let mut debouncer = Debouncer::new(DEFAULT_DURATION); | ||
assert!(debouncer.update(1).is_none()); | ||
assert!(debouncer.update(2).is_none()); | ||
assert!(debouncer.update(3).is_none()); | ||
std::thread::sleep(DEFAULT_DURATION); | ||
assert_eq!(debouncer.update(4), Some(4)); | ||
assert!(debouncer.query().is_none()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
use crate::TaskTable; | ||
|
||
const PANE_SIZE_RATIO: f32 = 3.0 / 4.0; | ||
|
||
#[derive(Debug, Clone, Copy)] | ||
pub struct SizeInfo { | ||
task_width_hint: u16, | ||
rows: u16, | ||
cols: u16, | ||
} | ||
|
||
impl SizeInfo { | ||
pub fn new<'a>(rows: u16, cols: u16, tasks: impl Iterator<Item = &'a str>) -> Self { | ||
let task_width_hint = TaskTable::width_hint(tasks); | ||
Self { | ||
rows, | ||
cols, | ||
task_width_hint, | ||
} | ||
} | ||
|
||
pub fn resize(&mut self, rows: u16, cols: u16) { | ||
self.rows = rows; | ||
self.cols = cols; | ||
} | ||
|
||
pub fn pane_rows(&self) -> u16 { | ||
self.rows | ||
// Account for header and footer in layout | ||
.saturating_sub(2) | ||
// Always allocate at least one row as vt100 crashes if emulating a zero area terminal | ||
.max(1) | ||
} | ||
|
||
pub fn task_list_width(&self) -> u16 { | ||
self.cols - self.pane_cols() | ||
} | ||
|
||
pub fn pane_cols(&self) -> u16 { | ||
// Want to maximize pane width | ||
let ratio_pane_width = (f32::from(self.cols) * PANE_SIZE_RATIO) as u16; | ||
let full_task_width = self.cols.saturating_sub(self.task_width_hint); | ||
full_task_width | ||
.max(ratio_pane_width) | ||
// We need to account for the left border of the pane | ||
.saturating_sub(1) | ||
} | ||
} |
Oops, something went wrong.