From 6c1f089f130b066d7a285d1b668148557bcddf71 Mon Sep 17 00:00:00 2001 From: Wez Furlong Date: Sat, 4 Aug 2018 19:02:46 -0700 Subject: [PATCH] buffer processing in Performer::print This helps to accumulate multi-char grapheme sequences into a single cell. --- term/src/screen.rs | 17 ++----- term/src/terminal.rs | 5 +-- term/src/terminalstate.rs | 93 ++++++++++++++++++++++++++++----------- 3 files changed, 72 insertions(+), 43 deletions(-) diff --git a/term/src/screen.rs b/term/src/screen.rs index 5bcc99c6b..0d6ddd9aa 100644 --- a/term/src/screen.rs +++ b/term/src/screen.rs @@ -114,27 +114,18 @@ impl Screen { /// Set a cell. the x and y coordinates are relative to the visible screeen /// origin. 0,0 is the top left. - pub fn set_cell( - &mut self, - x: usize, - y: VisibleRowIndex, - c: char, - attr: &CellAttributes, - ) -> &Cell { + pub fn set_cell(&mut self, x: usize, y: VisibleRowIndex, cell: &Cell) -> &Cell { let line_idx = self.phys_row(y); - debug!( - "set_cell {} x={} y={} phys={} {:?}", - c, x, y, line_idx, attr - ); + debug!("set_cell x={} y={} phys={} {:?}", x, y, line_idx, cell); let line = self.line_mut(line_idx); line.invalidate_implicit_links(); - if attr.hyperlink.is_some() { + if cell.attrs().hyperlink.is_some() { line.set_has_hyperlink(true); } - line.set_cell(x, Cell::new(c, attr.clone())) + line.set_cell(x, cell.clone()) } pub fn clear_line( diff --git a/term/src/terminal.rs b/term/src/terminal.rs index 152ce8f86..d749a2c5c 100644 --- a/term/src/terminal.rs +++ b/term/src/terminal.rs @@ -80,10 +80,7 @@ impl Terminal { pub fn advance_bytes>(&mut self, bytes: B, host: &mut TerminalHost) { let bytes = bytes.as_ref(); - let mut performer = Performer { - state: &mut self.state, - host, - }; + let mut performer = Performer::new(&mut self.state, host); self.parser.parse(bytes, |action| performer.perform(action)); } diff --git a/term/src/terminalstate.rs b/term/src/terminalstate.rs index 152e3d79e..f1cb6a209 100644 --- a/term/src/terminalstate.rs +++ b/term/src/terminalstate.rs @@ -1225,9 +1225,9 @@ impl TerminalState { let limit = (x + n as usize).min(self.screen().physical_cols); { let screen = self.screen_mut(); - let blank = CellAttributes::default(); + let blank = Cell::default(); for x in x..limit as usize { - screen.set_cell(x, y, ' ', &blank); + screen.set_cell(x, y, &blank); } } self.clear_selection_if_intersects(x..limit, y as ScrollbackOrVisibleRowIndex); @@ -1414,6 +1414,7 @@ impl TerminalState { pub(crate) struct Performer<'a> { pub state: &'a mut TerminalState, pub host: &'a mut TerminalHost, + print: Option, } impl<'a> Deref for Performer<'a> { @@ -1430,7 +1431,65 @@ impl<'a> DerefMut for Performer<'a> { } } +impl<'a> Drop for Performer<'a> { + fn drop(&mut self) { + self.flush_print(); + } +} + impl<'a> Performer<'a> { + pub fn new(state: &'a mut TerminalState, host: &'a mut TerminalHost) -> Self { + Self { + state, + host, + print: None, + } + } + + fn flush_print(&mut self) { + let p = match self.print.take() { + Some(s) => s, + None => return, + }; + + for g in unicode_segmentation::UnicodeSegmentation::graphemes(p.as_str(), true) { + if self.wrap_next { + self.new_line(true); + } + + let x = self.cursor.x; + let y = self.cursor.y; + let width = self.screen().physical_cols; + + let pen = self.pen.clone(); + + // Assign the cell and extract its printable width + let print_width = { + let cell = self + .screen_mut() + .set_cell(x, y, &Cell::new_grapheme(g, pen.clone())); + // the max(1) here is to ensure that we advance to the next cell + // position for zero-width graphemes. We want to make sure that + // they occupy a cell so that we can re-emit them when we output them. + // If we didn't do this, then we'd effectively filter them out from + // the model, which seems like a lossy design choice. + cell.width().max(1) + }; + + self.clear_selection_if_intersects( + x..x + print_width, + y as ScrollbackOrVisibleRowIndex, + ); + + if x + print_width < width { + self.cursor.x += print_width; + self.wrap_next = false; + } else { + self.wrap_next = true; + } + } + } + pub fn perform(&mut self, action: Action) { match action { Action::Print(c) => self.print(c), @@ -1444,33 +1503,12 @@ impl<'a> Performer<'a> { /// Draw a character to the screen fn print(&mut self, c: char) { - if self.wrap_next { - self.new_line(true); - } - - let x = self.cursor.x; - let y = self.cursor.y; - let width = self.screen().physical_cols; - - let pen = self.pen.clone(); - - // Assign the cell and extract its printable width - let print_width = { - let cell = self.screen_mut().set_cell(x, y, c, &pen); - cell.width() - }; - - self.clear_selection_if_intersects(x..x + print_width, y as ScrollbackOrVisibleRowIndex); - - if x + print_width < width { - self.cursor.x += print_width; - self.wrap_next = false; - } else { - self.wrap_next = true; - } + // We buffer up the chars to increase the chances of correctly grouping graphemes into cells + self.print.get_or_insert_with(String::new).push(c); } fn control(&mut self, control: ControlCode) { + self.flush_print(); match control { ControlCode::LineFeed | ControlCode::VerticalTab | ControlCode::FormFeed => { self.new_line(true /* TODO: depend on terminal mode */) @@ -1488,6 +1526,7 @@ impl<'a> Performer<'a> { } fn csi_dispatch(&mut self, csi: CSI) { + self.flush_print(); match csi { CSI::Sgr(sgr) => self.state.perform_csi_sgr(sgr), CSI::Cursor(cursor) => self.state.perform_csi_cursor(cursor, self.host), @@ -1502,6 +1541,7 @@ impl<'a> Performer<'a> { } fn esc_dispatch(&mut self, esc: Esc) { + self.flush_print(); match esc { Esc::Code(EscCode::StringTerminator) => { // String Terminator (ST); explicitly has nothing to do here, as its purpose is @@ -1528,6 +1568,7 @@ impl<'a> Performer<'a> { } fn osc_dispatch(&mut self, osc: OperatingSystemCommand) { + self.flush_print(); match osc { OperatingSystemCommand::SetIconNameAndWindowTitle(title) | OperatingSystemCommand::SetWindowTitle(title) => {