Skip to content

Commit

Permalink
fix(termion): Add workaround for failing cursor detection
Browse files Browse the repository at this point in the history
  • Loading branch information
erak committed Mar 5, 2024
1 parent c4ce7e8 commit 0e2bdbb
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 2 deletions.
18 changes: 16 additions & 2 deletions src/backend/termion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ where
W: Write,
{
writer: W,
cursor: Option<(u16, u16)>,
}

impl<W> TermionBackend<W>
Expand All @@ -83,7 +84,10 @@ where
/// let backend = TermionBackend::new(stdout());
/// ```
pub const fn new(writer: W) -> Self {
Self { writer }
Self {
writer,
cursor: None,
}
}
}

Expand Down Expand Up @@ -137,11 +141,21 @@ where
}

fn get_cursor(&mut self) -> io::Result<(u16, u16)> {
termion::cursor::DetectCursorPos::cursor_pos(&mut self.writer).map(|(x, y)| (x - 1, y - 1))
match termion::cursor::DetectCursorPos::cursor_pos(&mut self.writer) {
Ok((x, y)) => {
let cursor = (x - 1, y - 1);
self.cursor = Some(cursor);
Ok(cursor)
}
Err(_) => self
.cursor
.ok_or_else(|| io::Error::other("No last known cursor position")),
}
}

fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> {
write!(self.writer, "{}", termion::cursor::Goto(x + 1, y + 1))?;
self.cursor = Some((x, y));
self.writer.flush()
}

Expand Down
55 changes: 55 additions & 0 deletions tests/backend_termion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,58 @@ fn backend_termion_should_only_write_diffs() -> Result<(), Box<dyn std::error::E

Ok(())
}

/// Retrieving the cursor from an in-memory buffer will fail in
/// `termion::cursor::DetectCursorPos::cursor_pos()`. If there is no known last cursor, retrieving
/// the cursor will ulimately fail.
///
/// This simulates a usage of `termion` where retrieving the cursor fails in a multi-threaded
/// environment: [](https://gitlab.redox-os.org/redox-os/termion/-/issues/173)
#[cfg(feature = "termion")]
#[test]
fn backend_termion_should_fail_on_missing_last_known_cursor(
) -> Result<(), Box<dyn std::error::Error>> {
use std::io::{self, Cursor};

use ratatui::{backend::TermionBackend, Terminal};

let mut bytes = Vec::new();
let mut stdout = Cursor::new(&mut bytes);

let backend = TermionBackend::new(&mut stdout);
let mut terminal = Terminal::new(backend)?;

let cursor = terminal.get_cursor();
let error_kind = cursor.unwrap_err().kind();
let expected_kind = io::ErrorKind::Other;

assert_eq!(error_kind, expected_kind);

Ok(())
}

/// Retrieving the cursor from an in-memory buffer will fail in
/// `termion::cursor::DetectCursorPos::cursor_pos()`. If a cursor was set explicitely, retrieving

Check warning on line 95 in tests/backend_termion.rs

View workflow job for this annotation

GitHub Actions / lint

"explicitely" should be "explicitly".
/// the cursor will fallback to its last known position.
///
/// This simulates a usage of `termion` where retrieving the cursor fails in a multi-threaded
/// environment: [](https://gitlab.redox-os.org/redox-os/termion/-/issues/173)
#[cfg(feature = "termion")]
#[test]
fn backend_termion_should_use_last_known_cursor() -> Result<(), Box<dyn std::error::Error>> {
use std::io::Cursor;

use ratatui::{backend::TermionBackend, Terminal};

let mut bytes = Vec::new();
let mut stdout = Cursor::new(&mut bytes);

let backend = TermionBackend::new(&mut stdout);
let mut terminal = Terminal::new(backend)?;

terminal.set_cursor(13, 12)?;

assert_eq!(terminal.get_cursor()?, (13, 12));

Ok(())
}

0 comments on commit 0e2bdbb

Please sign in to comment.