Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ libc = "0.2"
ctor = "1"
indoc = "2"
insta = "1"
vt100 = "0.16"

[target.'cfg(unix)'.dev-dependencies]
portable-pty = "0.9"
rio-vt = "0.5.1"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Dependency cannot be resolved

On Unix test targets, Cargo resolves this plain version requirement through crates.io, where the requested rio-vt package is unavailable, causing the test suite to fail before compilation.


[profile.dev]
debug = 1
14 changes: 7 additions & 7 deletions src/multiselect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -711,7 +711,7 @@ impl<'a, T> MultiSelect<'a, T> {
mod tests {
use crate::test::without_ansi;
#[cfg(unix)]
use crate::test::{capture_term, replay, snapshot};
use crate::test::{Parser, capture_term, replay, snapshot};

use super::*;
use indoc::indoc;
Expand Down Expand Up @@ -799,7 +799,7 @@ mod tests {
/// code (jdx/demand#129), which made the menu drift up on every keypress.
///
/// Drives the redraw path through a `Term::read_write_pair` whose writer is
/// a `SharedBuf`, replays the captured bytes through a vt100 emulator, and
/// a `SharedBuf`, replays the captured bytes through a terminal emulator, and
/// asserts that the cursor row is stable across iterations (i.e. the render
/// origin doesn't drift).
#[cfg(unix)]
Expand All @@ -813,10 +813,10 @@ mod tests {
.option(DemandOption::new("Nutella"));
ms.term = term;

// Start the vt100 cursor well below the top of the screen, so that an
// Start the emulator cursor well below the top of the screen, so that an
// upward off-by-one would actually shift the rendered area visibly
// rather than getting silently clamped at row 0.
let mut parser = vt100::Parser::new(40, 120, 0);
let mut parser = Parser::new(40, 120, 0);
parser.process(&b"\n".repeat(20));

let mut cursor_rows = Vec::new();
Expand All @@ -833,7 +833,7 @@ mod tests {
ms.handle_down().unwrap();
}

// After iter 1 the vt100 cursor settles at the end of the frame.
// After iter 1 the emulator cursor settles at the end of the frame.
// With the bug, every subsequent iter pulls it up by another row; with
// the fix it stays put.
let first = cursor_rows[0];
Expand All @@ -857,7 +857,7 @@ mod tests {
.option(DemandOption::new("c"));
ms.term = term;

let mut parser = vt100::Parser::new(40, 120, 0);
let mut parser = Parser::new(40, 120, 0);
parser.process(&b"\n".repeat(20));

// First render with all 3 options.
Expand Down Expand Up @@ -901,7 +901,7 @@ mod tests {
let width = ms.term.size().1 as usize;
assert!(width < 140, "test needs a label wider than the terminal");

let mut parser = vt100::Parser::new(40, width as u16, 0);
let mut parser = Parser::new(40, width as u16, 0);
parser.process(&b"\n".repeat(20));

for _ in 0..3 {
Expand Down
6 changes: 3 additions & 3 deletions src/select.rs
Original file line number Diff line number Diff line change
Expand Up @@ -543,7 +543,7 @@ impl<'a, T> Select<'a, T> {
mod tests {
use crate::test::without_ansi;
#[cfg(unix)]
use crate::test::{capture_term, replay, snapshot};
use crate::test::{Parser, capture_term, replay, snapshot};

use super::*;
use indoc::indoc;
Expand Down Expand Up @@ -623,7 +623,7 @@ mod tests {
/// leftovers, so the prompt visibly duplicated itself.
///
/// Drives three redraw cycles through a captured `Term`, replays the
/// bytes through a vt100 emulator, and asserts the screen holds one
/// bytes through a terminal emulator, and asserts the screen holds one
/// copy of the prompt.
#[cfg(unix)]
#[test]
Expand All @@ -646,7 +646,7 @@ mod tests {
select.cursor_y = i % select.options.len();
}

let mut parser = vt100::Parser::new(24, width as u16, 0);
let mut parser = Parser::new(24, width as u16, 0);
replay(&mut parser, &snapshot(&buf));
let screen = parser.screen().contents();
assert_eq!(
Expand Down
57 changes: 54 additions & 3 deletions src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,74 @@ pub fn without_ansi(s: &str) -> Cow<'_, str> {
}

/// A `Term` whose output is captured in memory, for driving a widget's
/// redraw path and replaying the bytes through a vt100 emulator.
/// redraw path and replaying the bytes through a terminal emulator.
///
/// `Term::read_write_pair` is `#[cfg(unix)]` in the console crate. CI also
/// runs on Windows, where there is simply no test seam — the redraw bugs
/// these cover were reported on Linux/macOS and the fixes are
/// platform-agnostic, so unix-only coverage is sufficient.
#[cfg(unix)]
pub use capture::{capture_term, replay, snapshot};
pub use capture::{Parser, capture_term, replay, snapshot};

#[cfg(unix)]
mod capture {
use console::Term;
use rio_vt::ansi::CursorShape;
use rio_vt::crosswords::formatter::FormatOptions;
use rio_vt::crosswords::{Crosswords, CrosswordsSize};
use rio_vt::event::{VoidListener, WindowId};
use rio_vt::performer::handler::Processor;
use std::fs::{File, OpenOptions};
use std::io::Write;
use std::os::fd::{AsRawFd, RawFd};
use std::sync::{Arc, Mutex};

/// Minimal vt100-shaped adapter over rio-vt: feed bytes with `process`
/// and read the screen back through `screen()`.
pub struct Parser {
term: Crosswords<VoidListener>,
processor: Processor,
}

impl Parser {
pub fn new(rows: u16, cols: u16, _scrollback: usize) -> Self {
Self {
term: Crosswords::new(
CrosswordsSize::new(cols as usize, rows as usize),
CursorShape::Block,
VoidListener,
WindowId::from(0),
0,
0,
),
processor: Processor::default(),
}
}

pub fn process(&mut self, bytes: &[u8]) {
self.processor.advance(&mut self.term, bytes);
}

pub fn screen(&self) -> Screen<'_> {
Screen { term: &self.term }
}
}

pub struct Screen<'a> {
term: &'a Crosswords<VoidListener>,
}

impl Screen<'_> {
pub fn contents(&self) -> String {
self.term.format(FormatOptions::plain())
}

pub fn cursor_position(&self) -> (u16, u16) {
let pos = self.term.cursor().pos;
(pos.row.0 as u16, pos.col.0 as u16)
}
}

/// `Term::read_write_pair` is bounded by `Write + Debug + AsRawFd + Send
/// + 'static`. It only uses the fd for its own `AsRawFd` impl — the
/// actual I/O goes through `Write::write_all` — so we can satisfy the
Expand Down Expand Up @@ -73,7 +124,7 @@ mod capture {
/// Unix TTY performs. The emulator does not apply ONLCR itself, so feeding
/// raw `\n` bytes would leave the cursor column unchanged and make redraw
/// assertions model a pipe rather than a terminal.
pub fn replay(parser: &mut vt100::Parser, output: &[u8]) {
pub fn replay(parser: &mut Parser, output: &[u8]) {
let mut tty_output = Vec::with_capacity(output.len());
for &byte in output {
if byte == b'\n' {
Expand Down