From ecb482f2978bfbfe396fa29968a4b72e01432c86 Mon Sep 17 00:00:00 2001 From: Florian Dehau Date: Sun, 2 Aug 2020 18:30:52 +0200 Subject: [PATCH] fix(backend): move the cursor when first diff is on second cell Both termion and crossterm backends were not moving the cursor if the first diff to draw was on the second cell. The condition triggering the cursor move has been updated to fix this. In addition, two tests have been added to avoid future regressions. --- .github/workflows/ci.yml | 4 +++ CHANGELOG.md | 4 +++ src/backend/crossterm.rs | 11 +++---- src/backend/termion.rs | 18 +++++------- tests/backend_termion.rs | 63 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 83 insertions(+), 17 deletions(-) create mode 100644 tests/backend_termion.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 27bbb20d..93574072 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,6 +47,8 @@ jobs: uses: actions-rs/cargo@v1 with: command: test + env: + RUST_BACKTRACE: full - name: "Clippy" uses: actions-rs/cargo@v1 with: @@ -75,3 +77,5 @@ jobs: with: command: test args: --no-default-features --features=crossterm --tests --examples + env: + RUST_BACKTRACE: full diff --git a/CHANGELOG.md b/CHANGELOG.md index b4c4a577..23abc80c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## To be released +### Fixes + +* Fix incorrect output when the first diff to draw is on the second cell of the terminal (#347). + ## v0.10.0 - 2020-07-17 ### Breaking changes diff --git a/src/backend/crossterm.rs b/src/backend/crossterm.rs index f9626cb3..c0a13bee 100644 --- a/src/backend/crossterm.rs +++ b/src/backend/crossterm.rs @@ -58,16 +58,13 @@ where let mut fg = Color::Reset; let mut bg = Color::Reset; let mut modifier = Modifier::empty(); - let mut last_y = 0; - let mut last_x = 0; - - map_error(queue!(string, MoveTo(0, 0)))?; + let mut last_pos: Option<(u16, u16)> = None; for (x, y, cell) in content { - if y != last_y || x != last_x + 1 { + // Move the cursor if the previous location was not (x - 1, y) + if !matches!(last_pos, Some(p) if x == p.0 + 1 && y == p.1) { map_error(queue!(string, MoveTo(x, y)))?; } - last_x = x; - last_y = y; + last_pos = Some((x, y)); if cell.modifier != modifier { let diff = ModifierDiff { from: modifier, diff --git a/src/backend/termion.rs b/src/backend/termion.rs index f3048b88..a71229a2 100644 --- a/src/backend/termion.rs +++ b/src/backend/termion.rs @@ -1,13 +1,13 @@ -use std::fmt; -use std::io; -use std::io::Write; - use super::Backend; use crate::{ buffer::Cell, layout::Rect, style::{Color, Modifier}, }; +use std::{ + fmt, + io::{self, Write}, +}; pub struct TermionBackend where @@ -82,15 +82,13 @@ where let mut fg = Color::Reset; let mut bg = Color::Reset; let mut modifier = Modifier::empty(); - let mut last_y = 0; - let mut last_x = 0; - write!(string, "{}", termion::cursor::Goto(1, 1)).unwrap(); + let mut last_pos: Option<(u16, u16)> = None; for (x, y, cell) in content { - if y != last_y || x != last_x + 1 { + // Move the cursor if the previous location was not (x - 1, y) + if !matches!(last_pos, Some(p) if x == p.0 + 1 && y == p.1) { write!(string, "{}", termion::cursor::Goto(x + 1, y + 1)).unwrap(); } - last_x = x; - last_y = y; + last_pos = Some((x, y)); if cell.modifier != modifier { write!( string, diff --git a/tests/backend_termion.rs b/tests/backend_termion.rs new file mode 100644 index 00000000..ab2882c0 --- /dev/null +++ b/tests/backend_termion.rs @@ -0,0 +1,63 @@ +#[cfg(feature = "termion")] +#[test] +fn backend_termion_should_only_write_diffs() -> Result<(), Box> { + use std::{fmt::Write, io::Cursor}; + + let mut bytes = Vec::new(); + let mut stdout = Cursor::new(&mut bytes); + { + use tui::{ + backend::TermionBackend, layout::Rect, widgets::Paragraph, Terminal, TerminalOptions, + Viewport, + }; + let backend = TermionBackend::new(&mut stdout); + let area = Rect::new(0, 0, 3, 1); + let mut terminal = Terminal::with_options( + backend, + TerminalOptions { + viewport: Viewport::fixed(area), + }, + )?; + terminal.draw(|f| { + f.render_widget(Paragraph::new("a"), area); + })?; + terminal.draw(|f| { + f.render_widget(Paragraph::new("ab"), area); + })?; + terminal.draw(|f| { + f.render_widget(Paragraph::new("abc"), area); + })?; + } + + let expected = { + use termion::{color, cursor, style}; + let mut s = String::new(); + // First draw + write!(s, "{}", cursor::Goto(1, 1))?; + s.push_str("a"); + write!(s, "{}", color::Fg(color::Reset))?; + write!(s, "{}", color::Bg(color::Reset))?; + write!(s, "{}", style::Reset)?; + write!(s, "{}", cursor::Hide)?; + // Second draw + write!(s, "{}", cursor::Goto(2, 1))?; + s.push_str("b"); + write!(s, "{}", color::Fg(color::Reset))?; + write!(s, "{}", color::Bg(color::Reset))?; + write!(s, "{}", style::Reset)?; + write!(s, "{}", cursor::Hide)?; + // Third draw + write!(s, "{}", cursor::Goto(3, 1))?; + s.push_str("c"); + write!(s, "{}", color::Fg(color::Reset))?; + write!(s, "{}", color::Bg(color::Reset))?; + write!(s, "{}", style::Reset)?; + write!(s, "{}", cursor::Hide)?; + // Terminal drop + write!(s, "{}", cursor::Show)?; + s + }; + assert_eq!(std::str::from_utf8(&bytes)?, expected); + + Ok(()) +}