Something went wrong. Try again.
atproto git client
Something went wrong. Try again.
Rust
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192//! `atgc about`: a panel of project info floating over a field of DNA.//!//! The field is [`crate::art`]'s and is laid down across the whole frame;//! what is here is the panel, which is punched out of it and drawn on top.//! Everything is cropped by the frame rather than fitted to it, so the field//! runs off all four edges and behind the panel instead of stopping short.//!//! The frame follows the terminal instead of being fixed at one size. See//! [`frame`] for how it is chosen, and [`draw_bare`] for what happens when//! the terminal is too small to hold any of this.//!//! The art used to be the top half of this file, which is how the OAuth//! callback page came to import a *command* module for it. It is drawn, not//! stored, in both places, and now from one place.
use crate::art::{self, Canvas, GUTTER, PAD, draw_bare, draw_panel, panel_lines, width_of};use anyhow::Result;use terminal_size::{Height, Width, terminal_size};
/// Frame drawn when nothing better is known: a pipe, a redirect, or a/// terminal that will not report a size. Fixed on purpose: redirected/// output should not change shape with whatever the ambient terminal happens/// to be, the same reason [`about`] gates color on/// [`crate::term::hyperlink::stdout_escapes_wanted`]. These are the numbers/// `atgc about` drew at before it learned to look.////// 74 columns is a floor rather than the width: it is narrower than the/// panel plus its margins, so in practice the panel wins and the frame comes/// out 80 wide. Left as it was, so piped output is byte-for-byte unchanged.const FALLBACK_COLUMNS: i32 = 74;const FALLBACK_ROWS: i32 = 19;
/// Terminal rows left unspent. One keeps the shell's next prompt off the/// last row of the art; the second absorbs a prompt that wraps to two lines,/// which is common enough (git branch, venv, exit status) to be worth a row.const RESERVED_ROWS: i32 = 2;
/// Ceilings on the frame. The panel is the subject and the field is the mat/// it hangs on; past a helix or so of field on each side (`BAND_GAP` is 18/// columns) more of it only turns the panel into a stamp on a wall of/// wallpaper. 120 columns leaves the ~68-column panel a little over a full/// helix of field either side, and 31 rows is 2.5 turns of twist, up from/// the 1.5 turns the old fixed frame showed: enough to read the pattern as/// a helix rather than as tiling. Both also bound the canvas allocation, so/// a terminal reporting something absurd cannot ask for a huge one.const MAX_COLUMNS: i32 = 120;const MAX_ROWS: i32 = 31;
/// A positive integer from the environment, or nothing. Anything else:/// unset, empty, `0`, `-1`, `wide`: counts as not asked for.fn env_dimension(key: &str) -> Option<i32> { let raw = std::env::var(key).ok()?; raw.trim().parse::<i32>().ok().filter(|n| *n > 0)}
/// The frame to draw into, in cells, honoring in order: an explicit/// `COLUMNS`/`LINES` in the environment, then the size the terminal reports/// for stdout, then the fixed fallback. The two resolve independently, so/// `COLUMNS=40 atgc about` can narrow the frame without also having to say/// how tall it is.////// `COLUMNS` and `LINES` outrank the terminal because they are the only way/// to ask for a particular frame, and because neither bash nor zsh exports/// them: one that reaches this process was put there deliberately, on this/// command line or by a script that meant it. With neither set and stdout/// not a terminal, `terminal_size` returns `None` and the fallback stands:/// so a pipe or a redirect draws the same frame every time.////// The result is clamped from above but not from below. It can come back/// smaller than the panel, which is [`about`]'s cue to drop to bare lines.fn frame(panel_w: i32, panel_h: i32) -> (i32, i32) { let reported = terminal_size(); let columns = env_dimension("COLUMNS").or_else(|| reported.map(|(Width(w), _)| i32::from(w))); let rows = env_dimension("LINES").or_else(|| reported.map(|(_, Height(h))| i32::from(h)));
let width = match columns { Some(w) => w.min(MAX_COLUMNS), None => FALLBACK_COLUMNS.max(panel_w + 2 * (GUTTER + 4)), }; let height = match rows { Some(h) => { let spare = (h - RESERVED_ROWS).min(MAX_ROWS); let snapped = art::snap_rows(spare); // Snapping costs up to five rows, which a short terminal has not // got: below a panel with a row of field above and below it, // take the untrimmed height and let the bottom edge taper. if snapped >= panel_h + 2 { snapped } else { spare } } // No reserve subtracted here — there is no prompt to leave room for, // and this height is already a whole number of half turns. None => FALLBACK_ROWS, }; (width, height)}
/// The row the panel's top edge goes on, in a frame of `height` rows: the/// slack split between the two, floored.////// The flooring is the point rather than an off-by-one. A block sitting/// below the arithmetic middle reads as sagging, and this is what stops it/// ever being there: the field left above the panel is never deeper than the/// field left below it. Whether the panel also gets the half row of *lift*/// an optical centre wants is a matter of parity and belongs to the panel/// rather than to this function — [`art::snap_rows`] returns none but odd/// heights, so an even panel is lifted and an odd one, which is what/// [`art::panel_lines`] builds today, lands on the exact middle row.////// On the short unsnapped heights the slack is a row or two and the panel/// sits wherever it fits; nudging it further there would only shove it/// against the top edge.fn top_of(height: i32, panel_h: i32) -> i32 { (height - panel_h) / 2}
pub(crate) fn about() -> Result<()> { let lines = panel_lines();
let content = lines.iter().map(width_of).max().unwrap_or(0) as i32; let panel_w = content + 2 * PAD + 2; let panel_h = lines.len() as i32 + 2; let (width, height) = frame(panel_w, panel_h);
// Under the panel's own size there is no frame worth drawing: the border // would run off the edge mid-line, which reads as a bug rather than as // art. The field needs no threshold of its own — it is laid down edge to // edge and then cleared for `GUTTER` columns around the panel, so as the // frame tightens the field is squeezed out a column at a time and is // simply gone by the time the panel fills the width. let canvas = if width < panel_w || height < panel_h { draw_bare(&lines, content) } else { let mut canvas = Canvas::new(width, height); art::draw_field(&mut canvas); // Centered both ways; see [`top_of`] for which way the odd row goes. let top = top_of(height, panel_h); draw_panel(&mut canvas, (width - panel_w) / 2, top, panel_w, &lines); canvas };
let colors = crate::term::hyperlink::stdout_escapes_wanted(); print!("{}", canvas.render(colors)); Ok(())}
#[cfg(test)]mod tests { use super::{art, env_dimension}; use crate::art::panel_lines;
/// What [`super::top_of`] promises: the panel never sags. At every frame /// height it can be drawn in — snapped or not — the field above it is no /// deeper than the field below. #[test] fn the_panel_never_sits_below_the_middle() { let panel_h = panel_lines().len() as i32 + 2; for rows in panel_h..=super::MAX_ROWS { let above = super::top_of(rows, panel_h); let below = rows - panel_h - above; assert!(above >= 0, "{rows} rows put the panel at {above}"); assert!(above <= below, "{rows} rows: {above} above, {below} below"); } }
/// Fixed points: a height that already spans whole half turns is left /// alone, so a terminal at one of these sizes gives up no rows at all. #[test] fn snap_rows_leaves_exact_heights_alone() { for rows in [1, 7, 13, 19, 25, 31] { assert_eq!(art::snap_rows(rows), rows); } // And the fallback height is one of them, which is why the piped // frame needs no snapping. assert_eq!(art::snap_rows(super::FALLBACK_ROWS), super::FALLBACK_ROWS); }
/// `COLUMNS=0`, `LINES=-1` and `COLUMNS=wide` all mean "not asked for" /// rather than a frame of that size. /// /// Only the unset case is exercised: setting an environment variable is /// `unsafe` in edition 2024, and this crate forbids `unsafe_code`, so a /// test cannot put a value there to read back. The parsing above it is /// covered by inspection and by resizing a real terminal. #[test] fn an_unset_dimension_is_not_asked_for() { assert_eq!(env_dimension("ATGC_TEST_DIMENSION_THAT_IS_NOT_SET"), None); }}