Skip to content

Commit

Permalink
Fix ANSI escape codes provided in input from being stripped when rend…
Browse files Browse the repository at this point in the history
…ering

Fixes #248

> A recent commit (possibly 8e515d1#diff-546b6385118f60f64674170f786acf59f0ccce53d5d6ad4400409fc8363cfce1R74) has made it so that ANSI escape codes are now stripped. This makes it impossible to have colorised text inside prompt messages, e.g. if you use Confirm with a string that contains color text the color won't show up. This is a regression as this was possible with older versions (at least with 0.5.0).
  • Loading branch information
mikaelmello committed Apr 23, 2024
1 parent 7fe8e5e commit bf40860
Show file tree
Hide file tree
Showing 4 changed files with 185 additions and 1 deletion.
3 changes: 3 additions & 0 deletions inquire/src/terminal/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ pub mod termion;
#[cfg_attr(docsrs, doc(cfg(feature = "console")))]
pub mod console;

#[cfg(test)]
pub(crate) mod test;

pub type TerminalSize = Dimension;

pub trait Terminal: Sized {
Expand Down
137 changes: 137 additions & 0 deletions inquire/src/terminal/test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
use std::{collections::VecDeque, fmt::Display};

use crate::ui::{Key, Styled};

use super::{Terminal, TerminalSize};

pub struct MockTerminal {
pub size: TerminalSize,
pub input: VecDeque<Key>,
pub output: VecDeque<MockTerminalToken>,
}

#[derive(Debug, PartialEq, Eq)]
pub enum MockTerminalToken {
Text(Styled<String>),
ClearLine,
ClearUntilNewLine,
CursorHide,
CursorShow,
CursorUp(u16),
CursorDown(u16),
CursorLeft(u16),
CursorRight(u16),
CursorMoveToColumn(u16),
}

impl<T> From<T> for MockTerminalToken
where
T: Display,
{
fn from(val: T) -> Self {
MockTerminalToken::Text(Styled::new(val.to_string()))
}
}

impl MockTerminal {
pub fn new() -> Self {
Self {
size: TerminalSize::new(80, 40),
input: VecDeque::new(),
output: VecDeque::new(),
}
}

pub fn with_size(mut self, size: TerminalSize) -> Self {
self.size = size;
self
}

pub fn find_and_expect_token(&mut self, token: MockTerminalToken) {
while let Some(actual) = self.output.pop_front() {
if actual == token {
return;
}
}

panic!("Expected token not found: {:?}", token);
}
}

impl Terminal for MockTerminal {
fn get_size(&self) -> std::io::Result<TerminalSize> {
Ok(self.size)
}

fn write<T: Display>(&mut self, val: T) -> std::io::Result<()> {
let styled = Styled::new(format!("{}", val));
let token = MockTerminalToken::Text(styled);
self.output.push_back(token);
Ok(())
}

fn write_styled<T: Display>(&mut self, val: &Styled<T>) -> std::io::Result<()> {
let styled = Styled::new(format!("{}", val.content)).with_style_sheet(val.style);
let token = MockTerminalToken::Text(styled);
self.output.push_back(token);
Ok(())
}

fn clear_line(&mut self) -> std::io::Result<()> {
let token = MockTerminalToken::ClearLine;
self.output.push_back(token);
Ok(())
}

fn clear_until_new_line(&mut self) -> std::io::Result<()> {
let token = MockTerminalToken::ClearUntilNewLine;
self.output.push_back(token);
Ok(())
}

fn cursor_hide(&mut self) -> std::io::Result<()> {
let token = MockTerminalToken::CursorHide;
self.output.push_back(token);
Ok(())
}

fn cursor_show(&mut self) -> std::io::Result<()> {
let token = MockTerminalToken::CursorShow;
self.output.push_back(token);
Ok(())
}

fn cursor_up(&mut self, cnt: u16) -> std::io::Result<()> {
let token = MockTerminalToken::CursorUp(cnt);
self.output.push_back(token);
Ok(())
}

fn cursor_down(&mut self, cnt: u16) -> std::io::Result<()> {
let token = MockTerminalToken::CursorDown(cnt);
self.output.push_back(token);
Ok(())
}

fn cursor_left(&mut self, cnt: u16) -> std::io::Result<()> {
let token = MockTerminalToken::CursorLeft(cnt);
self.output.push_back(token);
Ok(())
}

fn cursor_right(&mut self, cnt: u16) -> std::io::Result<()> {
let token = MockTerminalToken::CursorRight(cnt);
self.output.push_back(token);
Ok(())
}

fn cursor_move_to_column(&mut self, idx: u16) -> std::io::Result<()> {
let token = MockTerminalToken::CursorMoveToColumn(idx);
self.output.push_back(token);
Ok(())
}

fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
11 changes: 11 additions & 0 deletions inquire/src/ui/api/style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,3 +216,14 @@ where
Self::new(from)
}
}

impl<T> PartialEq for Styled<T>
where
T: PartialEq + Display,
{
fn eq(&self, other: &Self) -> bool {
self.style == other.style && self.content == other.content
}
}

impl<T> Eq for Styled<T> where T: Eq + Display {}
35 changes: 34 additions & 1 deletion inquire/src/ui/frame_renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,10 @@ impl FrameState {

let current_char = match piece {
AnsiAwareChar::Char(c) => c,
AnsiAwareChar::AnsiEscapeSequence(_) => {
AnsiAwareChar::AnsiEscapeSequence(seq) => {
// we don't care for escape sequences when calculating cursor position
// and box size
self.current_styled.content.push_str(seq);
continue;
}
};
Expand Down Expand Up @@ -438,3 +439,35 @@ where
let _unused = self.terminal.flush();
}
}

#[cfg(test)]
mod test {
use crate::{
error::InquireResult,
terminal::{test::MockTerminal, TerminalSize},
};

use super::FrameRenderer;

#[test]
fn ensure_inline_ansi_codes_are_maintained() -> InquireResult<()> {
let terminal = MockTerminal::new().with_size(TerminalSize::new(200, 200));
let mut renderer = FrameRenderer::new(terminal)?;

renderer.start_frame()?;
renderer.write("Hello")?;
renderer.write("World")?;
renderer.write("\n")?;
renderer.write("\x1b[1;31mWhat\x1b[0m is your name?")?;
renderer.finish_current_frame(false)?;

let terminal = &mut renderer.terminal;

terminal.find_and_expect_token("Hello".into());
terminal.find_and_expect_token("World".into());
terminal.find_and_expect_token("\n".into());
terminal.find_and_expect_token("\x1b[1;31mWhat\x1b[0m is your name?".into());

Ok(())
}
}

0 comments on commit bf40860

Please sign in to comment.