ui: Add RawBuffer component for raw ansi content

master
Manos Pitsidianakis 2019-10-06 11:30:35 +03:00
parent 6f816d29c5
commit febea423d9
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
3 changed files with 350 additions and 4 deletions

View File

@ -2044,3 +2044,95 @@ impl<T: PartialEq + Debug + Clone + Sync + Send> Selector<T> {
.collect()
}
}
#[derive(Debug)]
pub struct RawBuffer {
pub buf: CellBuffer,
cursor: (usize, usize),
dirty: bool,
}
impl fmt::Display for RawBuffer {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Display::fmt("Raw buffer", f)
}
}
impl Component for RawBuffer {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
if self.dirty {
clear_area(grid, area);
let (width, height) = self.buf.size();
let (cols, rows) = (width!(area), height!(area));
self.cursor = (
std::cmp::min(width.saturating_sub(cols), self.cursor.0),
std::cmp::min(height.saturating_sub(rows), self.cursor.1),
);
clear_area(grid, area);
copy_area(
grid,
&self.buf,
area,
(
(
std::cmp::min((width - 1).saturating_sub(cols), self.cursor.0),
std::cmp::min((height - 1).saturating_sub(rows), self.cursor.1),
),
(
std::cmp::min(self.cursor.0 + cols, width - 1),
std::cmp::min(self.cursor.1 + rows, height - 1),
),
),
);
context.dirty_areas.push_back(area);
self.dirty = false;
}
}
fn process_event(&mut self, event: &mut UIEvent, _context: &mut Context) -> bool {
match *event {
UIEvent::Input(Key::Left) => {
self.cursor.0 = self.cursor.0.saturating_sub(1);
self.dirty = true;
true
}
UIEvent::Input(Key::Right) => {
self.cursor.0 = self.cursor.0 + 1;
self.dirty = true;
true
}
UIEvent::Input(Key::Up) => {
self.cursor.1 = self.cursor.1.saturating_sub(1);
self.dirty = true;
true
}
UIEvent::Input(Key::Down) => {
self.cursor.1 = self.cursor.1 + 1;
self.dirty = true;
true
}
_ => false,
}
}
fn is_dirty(&self) -> bool {
self.dirty
}
fn set_dirty(&mut self) {
self.dirty = true;
}
fn id(&self) -> ComponentId {
ComponentId::nil()
}
}
impl RawBuffer {
pub fn new(buf: CellBuffer) -> Self {
RawBuffer {
buf,
dirty: true,
cursor: (0, 0),
}
}
}

View File

@ -41,7 +41,7 @@ use termion::raw::IntoRawMode;
use termion::screen::AlternateScreen;
use termion::{clear, cursor, style};
type StateStdout = termion::screen::AlternateScreen<termion::raw::RawTerminal<std::io::Stdout>>;
pub type StateStdout = termion::screen::AlternateScreen<termion::raw::RawTerminal<std::io::Stdout>>;
struct InputHandler {
rx: Receiver<bool>,
@ -456,10 +456,10 @@ impl State {
for x in x_start..=x_end {
let c = self.grid[(x, y)];
if c.bg() != Color::Default {
write!(self.stdout(), "{}", termion::color::Bg(c.bg().as_termion())).unwrap();
c.bg().write_bg(self.stdout()).unwrap();
}
if c.fg() != Color::Default {
write!(self.stdout(), "{}", termion::color::Fg(c.fg().as_termion())).unwrap();
c.fg().write_fg(self.stdout()).unwrap();
}
if c.attrs() != Attr::Default {
write!(self.stdout(), "\x1B[{}m", c.attrs() as u8).unwrap();

View File

@ -31,7 +31,7 @@ use text_processing::wcwidth;
use std::convert::From;
use std::fmt;
use std::ops::{Deref, DerefMut, Index, IndexMut};
use termion::color::AnsiValue;
use termion::color::{AnsiValue, Rgb as TermionRgb};
/// Types and implementations taken from rustty for convenience.
@ -161,6 +161,8 @@ impl CellBuffer {
pub fn resize(&mut self, newcols: usize, newrows: usize, blank: Cell) {
let newlen = newcols * newrows;
if self.buf.len() == newlen {
self.cols = newcols;
self.rows = newrows;
return;
}
let mut newbuf: Vec<Cell> = Vec::with_capacity(newlen);
@ -527,6 +529,7 @@ pub enum Color {
Cyan,
White,
Byte(u8),
Rgb(u8, u8, u8),
Default,
}
@ -543,10 +546,41 @@ impl Color {
Color::Cyan => 0x06,
Color::White => 0x07,
Color::Byte(b) => b,
Color::Rgb(_, _, _) => unreachable!(),
Color::Default => 0x00,
}
}
pub fn from_byte(val: u8) -> Self {
match val {
0x00 => Color::Black,
0x01 => Color::Red,
0x02 => Color::Green,
0x03 => Color::Yellow,
0x04 => Color::Blue,
0x05 => Color::Magenta,
0x06 => Color::Cyan,
0x07 => Color::White,
_ => Color::Default,
}
}
pub fn write_fg(self, stdout: &mut crate::StateStdout) -> std::io::Result<()> {
use std::io::Write;
match self {
Color::Rgb(r, g, b) => write!(stdout, "{}", termion::color::Fg(TermionRgb(r, g, b))),
_ => write!(stdout, "{}", termion::color::Fg(self.as_termion())),
}
}
pub fn write_bg(self, stdout: &mut crate::StateStdout) -> std::io::Result<()> {
use std::io::Write;
match self {
Color::Rgb(r, g, b) => write!(stdout, "{}", termion::color::Bg(TermionRgb(r, g, b))),
_ => write!(stdout, "{}", termion::color::Bg(self.as_termion())),
}
}
pub fn as_termion(self) -> AnsiValue {
match self {
b @ Color::Black
@ -559,6 +593,7 @@ impl Color {
| b @ Color::White
| b @ Color::Default => AnsiValue(b.as_byte()),
Color::Byte(b) => AnsiValue(b as u8),
Color::Rgb(_, _, _) => AnsiValue(0),
}
}
}
@ -833,3 +868,222 @@ pub fn center_area(area: Area, (width, height): (usize, usize)) -> Area {
),
)
}
pub mod ansi {
use super::{Cell, CellBuffer, Color};
pub fn ansi_to_cellbuffer(s: &str) -> Option<CellBuffer> {
let mut buf: Vec<Cell> = Vec::with_capacity(2048);
enum State {
Start,
Csi,
SetFg,
SetBg,
}
use State::*;
let mut rows = 0;
let mut cols = 0;
let mut current_fg = Color::Default;
let mut current_bg = Color::Default;
let mut cur_cell;
let mut state: State;
for l in s.lines() {
cur_cell = Cell::default();
state = State::Start;
let mut chars = l.chars().peekable();
cols = 0;
rows += 1;
'line_loop: loop {
let c = chars.next();
if c.is_none() {
break 'line_loop;
}
match (&state, c.unwrap()) {
(Start, '\x1b') => {
if chars.next() != Some('[') {
return None;
}
state = Csi;
}
(Start, c) => {
cur_cell.set_ch(c);
cur_cell.set_fg(current_fg);
cur_cell.set_bg(current_bg);
buf.push(cur_cell);
cur_cell = Cell::default();
cols += 1;
}
(Csi, 'm') => {
/* Reset styles */
current_fg = Color::Default;
current_bg = Color::Default;
state = Start;
}
(Csi, '0') => {
if chars.next() != Some('m') {
return None;
}
/* Reset styles */
current_fg = Color::Default;
current_bg = Color::Default;
state = Start;
}
(Csi, '3') => {
match chars.next() {
Some('8') => {
/* Set foreground color */
if chars.next() == Some(';') {
state = SetFg;
/* Next arguments are 5;n or 2;r;g;b */
continue;
}
return None;
}
Some(c) if c >= '0' && c < '8' => {
current_fg = Color::from_byte(c as u8 - 0x30);
if chars.next() != Some('m') {
return None;
}
state = Start;
}
_ => return None,
}
}
(Csi, '4') => {
match chars.next() {
Some('8') => {
/* Set background color */
if chars.next() == Some(';') {
state = SetBg;
/* Next arguments are 5;n or 2;r;g;b */
continue;
}
return None;
}
Some(c) if c >= '0' && c < '8' => {
current_bg = Color::from_byte(c as u8 - 0x30);
if chars.next() != Some('m') {
return None;
}
state = Start;
}
_ => return None,
}
}
(SetFg, '5') => {
if chars.next() != Some(';') {
return None;
}
let mut accum = 0;
while chars.peek().is_some() && chars.peek() != Some(&'m') {
let c = chars.next().unwrap();
accum *= 10;
accum += c as u8 - 0x30;
}
if chars.next() != Some('m') {
return None;
}
current_fg = Color::from_byte(accum);
state = Start;
}
(SetFg, '2') => {
if chars.next() != Some(';') {
return None;
}
let mut rgb_color = Color::Rgb(0, 0, 0);
if let Color::Rgb(ref mut r, ref mut g, ref mut b) = rgb_color {
'rgb_fg: for val in &mut [r, g, b] {
let mut accum = 0;
while chars.peek().is_some()
&& chars.peek() != Some(&';')
&& chars.peek() != Some(&'m')
{
let c = chars.next().unwrap();
accum *= 10;
accum += c as u8 - 0x30;
}
**val = accum;
match chars.peek() {
Some(&'m') => {
break 'rgb_fg;
}
Some(&';') => {
chars.next();
}
_ => return None,
}
}
}
if chars.next() != Some('m') {
return None;
}
current_fg = rgb_color;
state = Start;
}
(SetBg, '5') => {
if chars.next() != Some(';') {
return None;
}
let mut accum = 0;
while chars.peek().is_some() && chars.peek() != Some(&'m') {
let c = chars.next().unwrap();
accum *= 10;
accum += c as u8 - 0x30;
}
if chars.next() != Some('m') {
return None;
}
current_bg = Color::from_byte(accum);
state = Start;
}
(SetBg, '2') => {
if chars.next() != Some(';') {
return None;
}
let mut rgb_color = Color::Rgb(0, 0, 0);
if let Color::Rgb(ref mut r, ref mut g, ref mut b) = rgb_color {
'rgb_bg: for val in &mut [r, g, b] {
let mut accum = 0;
while chars.peek().is_some()
&& chars.peek() != Some(&';')
&& chars.peek() != Some(&'m')
{
let c = chars.next().unwrap();
accum *= 10;
accum += c as u8 - 0x30;
}
**val = accum;
match chars.peek() {
Some(&'m') => {
break 'rgb_bg;
}
Some(&';') => {
chars.next();
}
_ => return None,
}
}
}
if chars.next() != Some('m') {
return None;
}
current_bg = rgb_color;
state = Start;
}
_ => unreachable!(),
}
}
}
if buf.len() != rows * cols {
debug!("rows: {} cols: {}, buf.len() = {}", rows, cols, buf.len());
}
Some(CellBuffer {
buf,
rows,
cols,
ascii_drawing: false,
})
}
}