use std::time::Duration;
use shep_core::protocol::SheepConfigView;
use super::app::RowKey;
use super::link::FLOCK_POLL;
use super::pane_bleats::BleatsPane;
use super::view::sheep::column_len;
use super::viewport::Viewport;
#[must_use]
pub fn window(body_cells: usize) -> Duration {
FLOCK_POLL * body_cells as u32
}
#[must_use]
pub fn scale_top(peak: f64, floor: f64) -> f64 {
let peak = peak.max(floor);
let decade = 10f64.powf(peak.log10().floor());
for step in [1.0, 2.0, 5.0, 10.0] {
let candidate = step * decade;
if candidate >= peak {
return candidate;
}
}
10.0 * decade
}
#[derive(Debug)]
pub struct SheepPane {
sheep: RowKey,
config: Option<SheepConfigView>,
feed: BleatsPane,
view: Viewport,
}
impl SheepPane {
#[must_use]
pub fn new(sheep: RowKey) -> Self {
let feed = BleatsPane::new(sheep.clone());
Self {
sheep,
config: None,
feed,
view: Viewport::new(),
}
}
#[must_use]
pub fn view(&self) -> &Viewport {
&self.view
}
fn body_len(&self) -> usize {
column_len(self.config())
}
pub(crate) fn move_by(&mut self, delta: isize) {
let len = self.body_len();
self.view
.set_rows(super::view::sheep::COLUMN_BODY_ROWS, len);
self.view.move_by(delta, len);
}
pub(crate) fn move_to_first(&mut self) {
let len = self.body_len();
self.view
.set_rows(super::view::sheep::COLUMN_BODY_ROWS, len);
self.view.move_to(0, len);
}
pub(crate) fn move_to_last(&mut self) {
let len = self.body_len();
self.view
.set_rows(super::view::sheep::COLUMN_BODY_ROWS, len);
self.view.move_to(len.saturating_sub(1), len);
}
#[must_use]
pub fn sheep(&self) -> &RowKey {
&self.sheep
}
#[must_use]
pub fn feed_sheep(&self) -> &RowKey {
self.feed.sheep()
}
#[must_use]
pub fn feed(&self) -> &BleatsPane {
&self.feed
}
pub(crate) fn feed_mut(&mut self) -> &mut BleatsPane {
&mut self.feed
}
#[must_use]
pub fn config(&self) -> Option<&SheepConfigView> {
self.config.as_ref()
}
pub fn adopt_config(&mut self, view: SheepConfigView) {
self.config = Some(view);
}
pub(crate) fn set_sheep(&mut self, sheep: RowKey) {
self.feed = BleatsPane::new(sheep.clone());
self.sheep = sheep;
self.config = None;
}
}
#[cfg(test)]
mod tests {
use shep_core::config::AppConfig;
use super::*;
#[test]
fn the_window_is_one_poll_per_cell() {
assert_eq!(window(140), Duration::from_secs(280));
assert_eq!(window(120), Duration::from_secs(240));
}
#[test]
fn a_scale_top_rounds_up_the_ladder() {
assert_eq!(scale_top(34.0, 2.0), 50.0);
assert_eq!(scale_top(6.0, 2.0), 10.0);
assert_eq!(scale_top(1.2, 2.0), 2.0);
}
#[test]
fn a_scale_top_never_falls_below_its_floor() {
assert_eq!(scale_top(0.01, 2.0), 2.0);
}
#[test]
fn the_panes_debug_names_no_field_value() {
let mut pane = SheepPane::new(RowKey::Sheep(9));
let config = AppConfig {
name: "web".into(),
cwd: Some("/home/ada/secret-project".into()),
args: vec!["--token".into(), "hunter2".into()],
..AppConfig::default()
};
pane.adopt_config(SheepConfigView::new(config, Vec::new(), Vec::new()));
assert_eq!(
format!("{pane:?}"),
r#"SheepPane { sheep: Sheep(9), config: Some(SheepConfigView { name: "web", env_keys: 0, env_secrets: 0, overridden: 0, pending: 0 }), feed: BleatsPane { sheep: Sheep(9), filters: Filters { stream: None, min_level: None, matcher: None, order: [] }, match_snapshot: None, scroll_offset: 0, following: true, body_rows: 0, width: 0, wrap: false }, view: Viewport { cursor: 0, offset: 0, rows: 0 } }"#
);
}
fn scrollable_pane() -> SheepPane {
let mut pane = SheepPane::new(RowKey::Sheep(1));
pane.adopt_config(SheepConfigView::new(
AppConfig::default(),
Vec::new(),
Vec::new(),
));
pane
}
#[test]
fn move_by_walks_the_column_and_clamps_at_both_ends() {
let mut pane = scrollable_pane();
let len = pane.body_len();
assert_eq!(pane.view().cursor(), 0);
pane.move_by(1);
assert_eq!(pane.view().cursor(), 1);
pane.move_by(-5);
assert_eq!(pane.view().cursor(), 0, "clamped at the top");
pane.move_by(isize::try_from(len).unwrap() + 5);
assert_eq!(pane.view().cursor(), len - 1, "clamped at the bottom");
}
#[test]
fn move_to_first_and_last_jump_to_either_end() {
let mut pane = scrollable_pane();
let len = pane.body_len();
pane.move_to_last();
assert_eq!(pane.view().cursor(), len - 1);
pane.move_to_first();
assert_eq!(pane.view().cursor(), 0);
}
#[test]
fn scrolling_past_the_visible_rows_advances_the_offset() {
let mut pane = scrollable_pane();
let len = pane.body_len();
assert!(
len > super::super::view::sheep::COLUMN_BODY_ROWS,
"setup: a default config must scroll: {len} lines, {} visible",
super::super::view::sheep::COLUMN_BODY_ROWS
);
for _ in 0..super::super::view::sheep::COLUMN_BODY_ROWS + 3 {
pane.move_by(1);
}
assert!(pane.view().offset() > 0, "offset: {}", pane.view().offset());
pane.move_to_last();
assert_eq!(
pane.view().offset(),
len - super::super::view::sheep::COLUMN_BODY_ROWS,
"the last page, not scrolled past the end"
);
}
}