ui: Add RawBuffer component for raw ansi content
parent
6f816d29c5
commit
febea423d9
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue