diff --git a/Cargo.lock b/Cargo.lock index ee074986..c9bead4c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -53,6 +53,7 @@ dependencies = [ "serde_derive 1.0.80 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)", "serde_yaml 0.8.7 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", "static_assertions 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "tempfile 3.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "terminfo 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 65293281..9ed83952 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,7 @@ static_assertions = "0.3.0" terminfo = "0.6.1" url = "1.7.1" time = "0.1.40" +smallvec = "0.6.6" [target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os="dragonfly", target_os="openbsd"))'.dependencies] x11-dl = "2" diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 9e15bd02..ee2d359e 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -758,7 +758,7 @@ impl<'a, T: 'a> DisplayIter<'a, T> { } } -impl<'a, T: Copy + 'a> Iterator for DisplayIter<'a, T> { +impl<'a, T: Copy + Clone + 'a> Iterator for DisplayIter<'a, T> { type Item = Indexed; #[inline] diff --git a/src/lib.rs b/src/lib.rs index 7ba3538b..da714a9e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -65,6 +65,7 @@ extern crate base64; extern crate terminfo; extern crate url; extern crate time; +extern crate smallvec; #[macro_use] pub mod macros; diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index ef5a1e76..0676d13d 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -31,7 +31,7 @@ use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher}; use Rgb; use config::{self, Config, Delta}; -use term::{self, cell, RenderableCell}; +use term::{self, cell, RenderableCell, CellContent}; use glutin::dpi::PhysicalSize; // Shader paths for live reload @@ -835,7 +835,7 @@ impl<'a> RenderApi<'a> { .map(|(i, c)| RenderableCell { line, column: col + i, - c, + c: CellContent::SingleChar(c), bg: color, fg: Rgb { r: 0, g: 0, b: 0 }, flags: cell::Flags::empty(), @@ -879,35 +879,38 @@ impl<'a> RenderApi<'a> { glyph_cache.font_key }; - let mut glyph_key = GlyphKey { - font_key, - size: glyph_cache.font_size, - c: cell.c, - }; - - // Don't render text of HIDDEN cells - if cell.flags.contains(cell::Flags::HIDDEN) { - glyph_key.c = ' '; - } - - // Add cell to batch - { - let glyph = glyph_cache.get(glyph_key, self); - self.add_render_item(&cell, glyph); - } - - // FIXME This is a super hacky way to do underlined text. During - // a time crunch to release 0.1, this seemed like a really - // easy, clean hack. - if cell.flags.contains(cell::Flags::UNDERLINE) { - let glyph_key = GlyphKey { + // TODO: Skip this iteration if cell is hidden, don't draw underline multiple times + for character in cell.c.iter() { + let mut glyph_key = GlyphKey { font_key, size: glyph_cache.font_size, - c: '_', + c: character, }; - let underscore = glyph_cache.get(glyph_key, self); - self.add_render_item(&cell, underscore); + // Don't render text of HIDDEN cells + if cell.flags.contains(cell::Flags::HIDDEN) { + glyph_key.c = ' '; + } + + // Add cell to batch + { + let glyph = glyph_cache.get(glyph_key, self); + self.add_render_item(&cell, glyph); + } + + // FIXME This is a super hacky way to do underlined text. During + // a time crunch to release 0.1, this seemed like a really + // easy, clean hack. + if cell.flags.contains(cell::Flags::UNDERLINE) { + let glyph_key = GlyphKey { + font_key, + size: glyph_cache.font_size, + c: '_', + }; + + let underscore = glyph_cache.get(glyph_key, self); + self.add_render_item(&cell, underscore); + } } } } diff --git a/src/term/cell.rs b/src/term/cell.rs index ef8509dc..9a986088 100644 --- a/src/term/cell.rs +++ b/src/term/cell.rs @@ -11,6 +11,13 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. +use std::fmt; + +use smallvec::SmallVec; +use serde::de::{SeqAccess, Visitor}; +use serde::ser::SerializeSeq; +use serde::{Serialize, Deserialize, Serializer, Deserializer}; + use ansi::{NamedColor, Color}; use grid; use index::Column; @@ -31,9 +38,128 @@ bitflags! { } } +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum CellContent { + SingleChar(char), + MultiChar([char; 3]), +} + +impl CellContent { + #[inline] + fn is_empty(&self) -> bool { + match *self { + CellContent::SingleChar(c) => c == ' ', + CellContent::MultiChar(_) => false, + } + } + + #[inline] + pub fn primary(&self) -> char { + match self { + CellContent::SingleChar(c) => *c, + CellContent::MultiChar(c) => c[0], + } + } + + #[inline] + pub fn iter<'a>(&'a self) -> CellContentIter<'a> { + CellContentIter::new(self) + } +} + +impl Serialize for CellContent { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + CellContent::SingleChar(c) => serializer.serialize_char(*c), + CellContent::MultiChar(c) => { + let mut seq = serializer.serialize_seq(Some(c.len()))?; + for element in c { + seq.serialize_element(&element)?; + } + seq.end() + }, + } + } +} + +impl<'de> Deserialize<'de> for CellContent { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct CellContentVisitor; + + impl<'a> Visitor<'a> for CellContentVisitor { + type Value = CellContent; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!(formatter, "a char or an array of chars") + } + + fn visit_char(self, value: char) -> ::std::result::Result + where E: ::serde::de::Error + { + Ok(CellContent::SingleChar(value)) + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'a>, + { + let mut array = [' ', 3]; + let index = 0; + while let Some(value) = seq.next_element::()? { + array[index] = value; + } + Ok(CellContent::MultiChar(array)) + } + } + + deserializer.deserialize_any(CellContentVisitor) + } +} + +pub struct CellContentIter<'a> { + inner: &'a CellContent, + index: usize, +} + +impl<'a> CellContentIter<'a> { + fn new(inner: &'a CellContent) -> CellContentIter<'a> { + CellContentIter { + inner, + index: 0, + } + } +} + +impl<'a> Iterator for CellContentIter<'a> { + type Item = char; + + fn next(&mut self) -> Option { + let res = match self.inner { + CellContent::SingleChar(c) => if self.index > 0 { + None + } else { + Some(*c) + }, + CellContent::MultiChar(c) => if self.index >= c.len() { + None + } else { + Some(c[self.index]) + } + }; + self.index += 1; + res + } +} + #[derive(Copy, Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] pub struct Cell { - pub c: char, + pub c: CellContent, pub fg: Color, pub bg: Color, pub flags: Flags, @@ -65,7 +191,7 @@ impl LineLength for grid::Row { } for (index, cell) in self[..].iter().rev().enumerate() { - if cell.c != ' ' { + if !cell.c.is_empty() { length = Column(self.len() - index); break; } @@ -93,7 +219,7 @@ impl Cell { pub fn new(c: char, fg: Color, bg: Color) -> Cell { Cell { - c, + c: CellContent::SingleChar(c), bg, fg, flags: Flags::empty(), @@ -102,9 +228,9 @@ impl Cell { #[inline] pub fn is_empty(&self) -> bool { - self.c == ' ' && - self.bg == Color::Named(NamedColor::Background) && - !self.flags.intersects(Flags::INVERSE | Flags::UNDERLINE) + self.c.is_empty() + && self.bg == Color::Named(NamedColor::Background) + && !self.flags.intersects(Flags::INVERSE | Flags::UNDERLINE) } #[inline] diff --git a/src/term/mod.rs b/src/term/mod.rs index 5107dc2d..68eee7f6 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -35,7 +35,7 @@ use logging::LoggerProxy; pub mod cell; pub mod color; -pub use self::cell::Cell; +pub use self::cell::{Cell, CellContent}; use self::cell::LineLength; const URL_SEPARATOR_CHARS: [char; 3] = [' ', '"', '\'']; @@ -62,7 +62,7 @@ impl Search for Term { let last_col = self.grid.num_cols() - Column(1); while let Some(cell) = iter.prev() { - if self.semantic_escape_chars.contains(cell.c) { + if self.semantic_escape_chars.contains(cell.c.primary()) { break; } @@ -84,7 +84,7 @@ impl Search for Term { let last_col = self.grid.num_cols() - Column(1); while let Some(cell) = iter.next() { - if self.semantic_escape_chars.contains(cell.c) { + if self.semantic_escape_chars.contains(cell.c.primary()) { break; } @@ -112,16 +112,16 @@ impl Search for Term { // Put all characters until separators into a string let mut buf = String::new(); while let Some(cell) = iterb.prev() { - if URL_SEPARATOR_CHARS.contains(&cell.c) { + if URL_SEPARATOR_CHARS.contains(&cell.c.primary()) { break; } - buf.insert(0, cell.c); + buf.insert(0, cell.c.primary()); } for cell in iterf { - if URL_SEPARATOR_CHARS.contains(&cell.c) { + if URL_SEPARATOR_CHARS.contains(&cell.c.primary()) { break; } - buf.push(cell.c); + buf.push(cell.c.primary()); } // Heuristic to remove all leading '(' @@ -290,7 +290,7 @@ impl<'a> RenderableCellsIter<'a> { cursor_cell.bg = cursor_color; let mut wide_cell = cursor_cell; - wide_cell.c = ' '; + wide_cell.c = CellContent::SingleChar(' '); self.push_cursor_cells(original_cell, cursor_cell, wide_cell); } @@ -300,11 +300,11 @@ impl<'a> RenderableCellsIter<'a> { let mut cursor_cell = self.grid[self.cursor]; let cursor_color = self.config.cursor_cursor_color().unwrap_or(cursor_cell.fg); - cursor_cell.c = cursor_cell_char; + cursor_cell.c = CellContent::SingleChar(cursor_cell_char); cursor_cell.fg = cursor_color; let mut wide_cell = cursor_cell; - wide_cell.c = wide_cell_char; + wide_cell.c = CellContent::SingleChar(wide_cell_char); self.push_cursor_cells(original_cell, cursor_cell, wide_cell); } @@ -424,7 +424,7 @@ pub struct RenderableCell { /// A _Display_ line (not necessarily an _Active_ line) pub line: Line, pub column: Column, - pub c: char, + pub c: CellContent, pub fg: Rgb, pub bg: Rgb, pub bg_alpha: f32, @@ -1029,7 +1029,9 @@ impl Term { } else if cols.start < line_end { for cell in &grid_line[cols.start..line_end] { if !cell.flags.contains(cell::Flags::WIDE_CHAR_SPACER) { - self.push(cell.c); + for character in cell.c.iter() { + self.push(character); + } } } @@ -1386,7 +1388,7 @@ impl ansi::Handler for Term { let cell = &mut self.grid[&self.cursor.point]; *cell = self.cursor.template; - cell.c = self.cursor.charsets[self.active_charset].map(c); + cell.c = CellContent::SingleChar(self.cursor.charsets[self.active_charset].map(c)); // Handle wide chars if width == 2 { @@ -1416,7 +1418,7 @@ impl ansi::Handler for Term { fn dectest(&mut self) { trace!("dectest"); let mut template = self.cursor.template; - template.c = 'E'; + template.c = CellContent::SingleChar('E'); self.grid.region_mut(..) .each(|c| c.reset(&template));