diff --git a/Cargo.toml b/Cargo.toml index 2a42ff7..84e9282 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,4 +32,5 @@ backoff = { version = "0.4.0", features = ["futures", "tokio"] } time = { version = "0.3", features = ["std"] } [profile.release] +panic = "abort" debug = 1 diff --git a/ansi-to-html/src/lib.rs b/ansi-to-html/src/lib.rs index 2e3b043..bab4aad 100644 --- a/ansi-to-html/src/lib.rs +++ b/ansi-to-html/src/lib.rs @@ -4,32 +4,32 @@ mod ansi; mod perform; mod renderer; -pub struct Handle { - renderer: renderer::Renderer, +pub struct Handle { + renderer: renderer::Renderer, parser: vte::Parser, } -impl Handle { - pub fn new() -> Self { +impl Handle { + pub fn new(mut out: W) -> Self { + out.write_all( + br#" +
"#
+        ).unwrap();
         Self {
-            renderer: renderer::Renderer::new(String::new()),
+            renderer: renderer::Renderer::new(out, String::new()),
             parser: vte::Parser::new(),
         }
     }
 
-    pub fn finish(&self, mut output: F) -> std::io::Result<()> {
-        output.write_all(
-            br#"
-            
"#
-        )?;
-        self.renderer.emit_html(&mut output)?;
-        output.write_all(b"
") + pub fn finish(&mut self) -> std::io::Result<()> { + self.renderer.emit_html()?; + self.renderer.out.write_all(b"
") } } -impl Write for Handle { +impl Write for Handle { fn write(&mut self, buf: &[u8]) -> std::io::Result { for b in buf { self.parser.advance(&mut self.renderer, *b); @@ -43,15 +43,17 @@ impl Write for Handle { } pub fn render(name: String, bytes: &[u8]) -> (String, String) { - let mut h = Handle { - renderer: renderer::Renderer::new(name), + let mut html = Vec::new(); + let mut handle = Handle { + renderer: renderer::Renderer::new(&mut html, name), parser: vte::Parser::new(), }; - h.write_all(&bytes).unwrap(); - let mut html = Vec::new(); - h.renderer.emit_html(&mut html).unwrap(); + handle.write_all(&bytes).unwrap(); + handle.renderer.emit_html().unwrap(); + let mut css = Vec::new(); - h.renderer.emit_css(&mut css).unwrap(); + handle.renderer.out = &mut css; + handle.renderer.emit_css().unwrap(); ( String::from_utf8(css).unwrap(), diff --git a/ansi-to-html/src/main.rs b/ansi-to-html/src/main.rs index 3c0f93a..482a94f 100644 --- a/ansi-to-html/src/main.rs +++ b/ansi-to-html/src/main.rs @@ -1,8 +1,9 @@ -use std::io::{copy, stdin, stdout}; +use std::io::{copy, stdin, stdout, BufWriter}; fn main() { env_logger::init(); - let mut handle = ansi_to_html::Handle::new(); + let out = BufWriter::new(stdout().lock()); + let mut handle = ansi_to_html::Handle::new(out); copy(&mut stdin().lock(), &mut handle).unwrap(); - handle.finish(stdout().lock()).unwrap(); + handle.finish().unwrap() } diff --git a/ansi-to-html/src/perform.rs b/ansi-to-html/src/perform.rs index a5fb363..cf019d5 100644 --- a/ansi-to-html/src/perform.rs +++ b/ansi-to-html/src/perform.rs @@ -1,8 +1,9 @@ use crate::ansi::{Color, C0}; use crate::renderer::Renderer; +use std::io::Write; use vte::{Params, ParamsIter, Perform}; -impl Perform for Renderer { +impl Perform for Renderer { fn print(&mut self, c: char) { self.print(c); } @@ -170,6 +171,7 @@ impl Perform for Renderer { } } +#[inline] fn parse_color(it: &mut ParamsIter) -> Option { match it.next() { Some(&[5]) => { diff --git a/ansi-to-html/src/renderer.rs b/ansi-to-html/src/renderer.rs index 0f85e96..1cf07fd 100644 --- a/ansi-to-html/src/renderer.rs +++ b/ansi-to-html/src/renderer.rs @@ -1,8 +1,13 @@ use crate::ansi::Color; use std::cell::RefCell; use std::collections::HashMap; +use std::collections::VecDeque; +use std::io::Write; -pub struct Renderer { +// This is the number of rows that inapty uses, should be good enough? +const MAX_ROWS: usize = 64; + +pub struct Renderer { pub name: String, pub bold: bool, pub italic: bool, @@ -11,8 +16,10 @@ pub struct Renderer { pub foreground: Color, pub background: Color, current_row: usize, - rows: Vec, + rows: VecDeque, styles: Styles, + pub out: W, + prev: Cell, } #[derive(Debug, Default)] @@ -48,13 +55,23 @@ struct Row { } impl Row { + const LEN: usize = 256; + fn new() -> Self { Row { - cells: Vec::new(), + cells: vec![Cell::default(); Row::LEN], position: 0, } } + fn clear(&mut self) { + self.cells.truncate(Row::LEN); + for c in &mut self.cells { + *c = Cell::default(); + } + self.position = 0; + } + fn erase(&mut self) { for c in &mut self.cells { c.text = ' '; @@ -65,20 +82,26 @@ impl Row { self.position = position; } - // FIXME: This misbehaves if the position is off in space + #[inline] fn print(&mut self, cell: Cell) { if let Some(current) = self.cells.get_mut(self.position) { *current = cell; } else { - self.cells.push(cell); + self.print_cold(cell); } self.position += 1; } + + #[cold] + #[inline(never)] + fn print_cold(&mut self, cell: Cell) { + self.cells.push(cell); + } } -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Cell { - text: char, // TODO: totally wrong, graphmeme clusters + text: char, // FIXME: totally wrong, graphmeme clusters foreground: Color, background: Color, bold: bool, @@ -101,8 +124,8 @@ impl Default for Cell { } } -impl Renderer { - pub fn new(name: String) -> Self { +impl Renderer { + pub fn new(out: W, name: String) -> Self { Self { name, bold: false, @@ -112,8 +135,10 @@ impl Renderer { foreground: Color::bright_white(), background: Color::black(), current_row: 0, - rows: vec![Row::new()], + rows: vec![Row::new()].into(), styles: Styles::default(), + out, + prev: Cell::default(), } } @@ -150,9 +175,19 @@ impl Renderer { } pub fn linefeed(&mut self) { - self.current_row += 1; - if self.current_row == self.rows.len() { - self.rows.push(Row::new()); + if self.current_row == MAX_ROWS - 1 { + // Pushing something off the screen + let mut row = self.rows.pop_front().unwrap(); + self.render(&row).unwrap(); + row.clear(); + self.rows.push_back(row); + } else if self.current_row == self.rows.len() - 1 { + // Not pushing something off screen, but we need a new row + self.rows.push_back(Row::new()); + self.current_row += 1; + } else { + // Moving within the screen + self.current_row += 1; } } @@ -183,9 +218,10 @@ impl Renderer { } pub fn handle_move(&mut self, row: u16, col: u16) { - self.current_row = row as usize; - while self.current_row >= self.rows.len() { - self.rows.push(Row::new()); + if row <= self.current_row as u16 { + self.move_up_by(self.current_row as u16 - row) + } else { + self.move_down_by(row - self.current_row as u16); } self.set_column(col); } @@ -195,9 +231,8 @@ impl Renderer { } pub fn move_down_by(&mut self, cells: u16) { - self.current_row += cells as usize; - while self.current_row >= self.rows.len() { - self.rows.push(Row::new()); + for _ in 0..cells { + self.linefeed(); } } @@ -210,6 +245,7 @@ impl Renderer { self.current_row().position = self.current_row().position.saturating_sub(cells as usize); } + #[inline] pub fn set_column(&mut self, cells: u16) { let row = self.current_row(); row.position = cells.saturating_sub(1) as usize; @@ -219,49 +255,50 @@ impl Renderer { let _ = &row.cells[..row.position]; } - pub fn emit_html(&self, mut out: W) -> std::io::Result<()> { - let mut prev = Cell { - text: ' ', - foreground: Color::bright_white(), - background: Color::black(), - bold: false, - italic: false, - underline: false, - dim: false, - }; - - out.write_all(b"")?; - - for row in &self.rows[..self.current_row] { - let row = &*row; - for cell in &row.cells { - // Terminal applications will often reset the style right after some formatted text - // then write some whitespace then set it to something again. - // So we only apply style changes if the cell is nonempty. This is a ~50% savings - // in emitted HTML. - if cell.text != ' ' { - if cell.bold != prev.bold || cell.foreground != prev.foreground { - self.styles.with(cell.foreground, cell.bold, |class| { - write!(out, "", class) - })?; - } - prev = cell.clone(); + #[inline] + fn render(&mut self, row: &Row) -> std::io::Result<()> { + for cell in &row.cells { + // Terminal applications will often reset the style right after some formatted text + // then write some whitespace then set it to something again. + // So we only apply style changes if the cell is nonempty. This is a ~50% savings + // in emitted HTML. + let text = cell.text; + if text != ' ' { + if cell.bold != self.prev.bold || cell.foreground != self.prev.foreground { + self.out.write_all(b"")?; } - match cell.text { - '<' => out.write_all(b"<")?, - '>' => out.write_all(b">")?, - c => write!(out, "{}", c)?, + self.prev = *cell; + } + match text { + '<' => self.out.write_all(b"<")?, + '>' => self.out.write_all(b">")?, + c => { + let mut bytes = [0u8; 4]; + let s = c.encode_utf8(&mut bytes); + self.out.write_all(s.as_bytes())?; } } - out.write_all(&[b'\n'])?; } - out.write_all(b"") + self.out.write_all(&[b'\n'])?; + Ok(()) + } + + pub fn emit_html(&mut self) -> std::io::Result<()> { + self.out.write_all(b"")?; + for row in core::mem::take(&mut self.rows) { + self.render(&row)?; + } + self.out.write_all(b"") } - pub fn emit_css(&self, mut out: W) -> std::io::Result<()> { + pub fn emit_css(&mut self) -> std::io::Result<()> { for ((color, bold), name) in self.styles.known.borrow().iter() { write!( - out, + self.out, ".{}{{color:{};font-weight:{}}}\n", name, color.as_str(), diff --git a/src/sync.rs b/src/sync.rs index 70a857e..9c7a968 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -124,6 +124,7 @@ async fn sync_all_html(client: Arc) -> Result> { } */ + log::info!("Rendering {:?}", krate); let rendered = render::render_crate(&krate, &raw); /* let mut header = tar::Header::new_gnu();