Files
codex/prs/bolinfest/study/PR-1710-study.md
2025-09-02 15:17:45 -07:00

4.0 KiB
Raw Blame History

DOs

  • Prefer typed commands over magic ANSI: Define small crossterm::Command wrappers so intent is obvious.
use std::fmt::{self, Write as _};
use crossterm::Command;

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SetScrollRegion(pub std::ops::Range<u16>);
impl Command for SetScrollRegion {
    fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
        // CSI {top};{bottom} r — set scroll region
        write!(f, "\x1b[{};{}r", self.0.start, self.0.end)
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ResetScrollRegion;
impl Command for ResetScrollRegion {
    fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
        // CSI r — reset scroll region
        write!(f, "\x1b[r")
    }
}
  • Comment raw ANSI if you must use it: If wrappers arent viable, explain the sequence inline.
// CSI 1;{top} r — restrict scroll area to rows 1..=top
queue!(std::io::stdout(), Print(format!("\x1b[1;{}r", area.top()))).ok();
// CSI r — reset scroll region to full screen
queue!(std::io::stdout(), Print("\x1b[r")).ok();
  • Compute wrapped height using Unicode display width: Count doublewidth characters and span concatenation.
use ratatui::text::Line;
use unicode_width::UnicodeWidthStr;

fn line_height(line: &Line, width: u16) -> u16 {
    if width == 0 { return 1; }
    let w: usize = line.spans.iter().map(|s| s.content.width()).sum();
    ((w as u16).div_ceil(width)).max(1)
}
  • Scroll viewport only when needed and keep it in sync: Make room when not at the bottom, then update the viewport area.
use ratatui::layout::Size;

let screen = terminal.backend().size().unwrap_or(Size::new(0, 0));
let mut area = terminal.get_frame().area();

let needed = wrapped_line_count(&lines, area.width);
if area.bottom() < screen.height {
    let n = needed.min(screen.height - area.bottom());
    terminal.backend_mut()
        .scroll_region_down(area.top()..screen.height, n)
        .ok();
    area.y += n;
    terminal.set_viewport_area(area);
}
  • Insert lines by limiting the scroll region, printing, then resetting: Place the cursor at the region end and print CRLF before spans.
use crossterm::{queue, style::Print};
use ratatui::layout::Position;

queue!(std::io::stdout(), SetScrollRegion(1..area.top())).ok();
terminal.set_cursor_position(Position::new(0, area.top() - 1)).ok();

for line in &lines {
    queue!(std::io::stdout(), Print("\r\n")).ok();
    write_spans(&mut std::io::stdout(), line.iter()).ok();
}

queue!(std::io::stdout(), ResetScrollRegion).ok();
  • Inline variables in format/write macros: Follow house style for clarity.
write!(f, "\x1b[{};{}r", start, end)?;
  • Handle edge cases explicitly: Guard zero widths and ensure at least one visual line.
let height = line_height(&line, area.width).max(1);

DONTs

  • Dont emit unexplained magic ANSI strings: Theyre hard to review and maintain.
// BAD: unclear intent
queue!(std::io::stdout(), Print("\x1b[1;24r")).ok();
  • Dont approximate wrapping with lines.len(): This ignores Unicode widths and span composition.
// BAD: ignores display width and wrapping
let wrapped = lines.len() as u16;
  • Dont move the cursor above the screen: Large inserts can underflow and place the cursor offscreen.
// BAD: can underflow when many lines insert
terminal.set_cursor_position(Position::new(0, area.top() - lines.len() as u16)).ok();
  • Dont forget to reset the scroll region: Leaving a restricted region breaks later rendering.
// BAD: missing reset
// queue!(std::io::stdout(), ResetScrollRegion).ok();
  • Dont scroll the whole screen linebyline: Prefer setting a scroll region and printing once for correctness and speed.
// BAD: inefficient and fails for multiline wraps
terminal.backend_mut().scroll_region_up(0..area.top(), 1).ok();
  • Dont keep unused imports: Trim them to reduce noise and lints.
// BAD: unused in this module
use ratatui::prelude::Backend;