| // Copyright 2016 Joe Wilm, The Alacritty Project Contributors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // 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 bitflags::bitflags; |
| |
| use serde::{Deserialize, Serialize}; |
| |
| use crate::ansi::{Color, NamedColor}; |
| use crate::grid::{self, GridCell}; |
| use crate::index::Column; |
| |
| // Maximum number of zerowidth characters which will be stored per cell. |
| pub const MAX_ZEROWIDTH_CHARS: usize = 5; |
| |
| bitflags! { |
| #[derive(Serialize, Deserialize)] |
| pub struct Flags: u16 { |
| const INVERSE = 0b00_0000_0001; |
| const BOLD = 0b00_0000_0010; |
| const ITALIC = 0b00_0000_0100; |
| const BOLD_ITALIC = 0b00_0000_0110; |
| const UNDERLINE = 0b00_0000_1000; |
| const WRAPLINE = 0b00_0001_0000; |
| const WIDE_CHAR = 0b00_0010_0000; |
| const WIDE_CHAR_SPACER = 0b00_0100_0000; |
| const DIM = 0b00_1000_0000; |
| const DIM_BOLD = 0b00_1000_0010; |
| const HIDDEN = 0b01_0000_0000; |
| const STRIKEOUT = 0b10_0000_0000; |
| } |
| } |
| |
| const fn default_extra() -> [char; MAX_ZEROWIDTH_CHARS] { |
| [' '; MAX_ZEROWIDTH_CHARS] |
| } |
| |
| #[derive(Copy, Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] |
| pub struct Cell { |
| pub c: char, |
| pub fg: Color, |
| pub bg: Color, |
| pub flags: Flags, |
| #[serde(default = "default_extra")] |
| pub extra: [char; MAX_ZEROWIDTH_CHARS], |
| } |
| |
| impl Default for Cell { |
| fn default() -> Cell { |
| Cell::new(' ', Color::Named(NamedColor::Foreground), Color::Named(NamedColor::Background)) |
| } |
| } |
| |
| impl GridCell for Cell { |
| #[inline] |
| fn is_empty(&self) -> bool { |
| (self.c == ' ' || self.c == '\t') |
| && self.extra[0] == ' ' |
| && self.bg == Color::Named(NamedColor::Background) |
| && self.fg == Color::Named(NamedColor::Foreground) |
| && !self |
| .flags |
| .intersects(Flags::INVERSE | Flags::UNDERLINE | Flags::STRIKEOUT | Flags::WRAPLINE) |
| } |
| |
| #[inline] |
| fn is_wrap(&self) -> bool { |
| self.flags.contains(Flags::WRAPLINE) |
| } |
| |
| #[inline] |
| fn set_wrap(&mut self, wrap: bool) { |
| if wrap { |
| self.flags.insert(Flags::WRAPLINE); |
| } else { |
| self.flags.remove(Flags::WRAPLINE); |
| } |
| } |
| } |
| |
| /// Get the length of occupied cells in a line |
| pub trait LineLength { |
| /// Calculate the occupied line length |
| fn line_length(&self) -> Column; |
| } |
| |
| impl LineLength for grid::Row<Cell> { |
| fn line_length(&self) -> Column { |
| let mut length = Column(0); |
| |
| if self[Column(self.len() - 1)].flags.contains(Flags::WRAPLINE) { |
| return Column(self.len()); |
| } |
| |
| for (index, cell) in self[..].iter().rev().enumerate() { |
| if cell.c != ' ' || cell.extra[0] != ' ' { |
| length = Column(self.len() - index); |
| break; |
| } |
| } |
| |
| length |
| } |
| } |
| |
| impl Cell { |
| #[inline] |
| pub fn bold(&self) -> bool { |
| self.flags.contains(Flags::BOLD) |
| } |
| |
| #[inline] |
| pub fn inverse(&self) -> bool { |
| self.flags.contains(Flags::INVERSE) |
| } |
| |
| #[inline] |
| pub fn dim(&self) -> bool { |
| self.flags.contains(Flags::DIM) |
| } |
| |
| pub fn new(c: char, fg: Color, bg: Color) -> Cell { |
| Cell { extra: [' '; MAX_ZEROWIDTH_CHARS], c, bg, fg, flags: Flags::empty() } |
| } |
| |
| #[inline] |
| pub fn reset(&mut self, template: &Cell) { |
| // memcpy template to self |
| *self = Cell { c: template.c, bg: template.bg, ..Cell::default() }; |
| } |
| |
| #[inline] |
| pub fn chars(&self) -> [char; MAX_ZEROWIDTH_CHARS + 1] { |
| unsafe { |
| let mut chars = [std::mem::MaybeUninit::uninit(); MAX_ZEROWIDTH_CHARS + 1]; |
| std::ptr::write(chars[0].as_mut_ptr(), self.c); |
| std::ptr::copy_nonoverlapping( |
| self.extra.as_ptr() as *mut std::mem::MaybeUninit<char>, |
| chars.as_mut_ptr().offset(1), |
| self.extra.len(), |
| ); |
| std::mem::transmute(chars) |
| } |
| } |
| |
| #[inline] |
| pub fn push_extra(&mut self, c: char) { |
| for elem in self.extra.iter_mut() { |
| if elem == &' ' { |
| *elem = c; |
| break; |
| } |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::{Cell, LineLength}; |
| |
| use crate::grid::Row; |
| use crate::index::Column; |
| |
| #[test] |
| fn line_length_works() { |
| let template = Cell::default(); |
| let mut row = Row::new(Column(10), &template); |
| row[Column(5)].c = 'a'; |
| |
| assert_eq!(row.line_length(), Column(6)); |
| } |
| |
| #[test] |
| fn line_length_works_with_wrapline() { |
| let template = Cell::default(); |
| let mut row = Row::new(Column(10), &template); |
| row[Column(9)].flags.insert(super::Flags::WRAPLINE); |
| |
| assert_eq!(row.line_length(), Column(10)); |
| } |
| } |
| |
| #[cfg(all(test, feature = "bench"))] |
| mod benches { |
| extern crate test; |
| use super::Cell; |
| |
| #[bench] |
| fn cell_reset(b: &mut test::Bencher) { |
| b.iter(|| { |
| let mut cell = Cell::default(); |
| |
| for _ in 0..100 { |
| cell.reset(test::black_box(&Cell::default())); |
| } |
| |
| test::black_box(cell); |
| }); |
| } |
| } |