term_model/term/
cell.rs

1// Copyright 2016 Joe Wilm, The Alacritty Project Contributors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14use bitflags::bitflags;
15
16use serde::{Deserialize, Serialize};
17
18use crate::ansi::{Color, NamedColor};
19use crate::grid::{self, GridCell};
20use crate::index::Column;
21
22// Maximum number of zerowidth characters which will be stored per cell.
23pub const MAX_ZEROWIDTH_CHARS: usize = 5;
24
25bitflags! {
26    #[derive(Serialize, Deserialize)]
27    pub struct Flags: u16 {
28        const INVERSE           = 0b00_0000_0001;
29        const BOLD              = 0b00_0000_0010;
30        const ITALIC            = 0b00_0000_0100;
31        const BOLD_ITALIC       = 0b00_0000_0110;
32        const UNDERLINE         = 0b00_0000_1000;
33        const WRAPLINE          = 0b00_0001_0000;
34        const WIDE_CHAR         = 0b00_0010_0000;
35        const WIDE_CHAR_SPACER  = 0b00_0100_0000;
36        const DIM               = 0b00_1000_0000;
37        const DIM_BOLD          = 0b00_1000_0010;
38        const HIDDEN            = 0b01_0000_0000;
39        const STRIKEOUT         = 0b10_0000_0000;
40    }
41}
42
43const fn default_extra() -> [char; MAX_ZEROWIDTH_CHARS] {
44    [' '; MAX_ZEROWIDTH_CHARS]
45}
46
47#[derive(Copy, Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
48pub struct Cell {
49    pub c: char,
50    pub fg: Color,
51    pub bg: Color,
52    pub flags: Flags,
53    #[serde(default = "default_extra")]
54    pub extra: [char; MAX_ZEROWIDTH_CHARS],
55}
56
57impl Default for Cell {
58    fn default() -> Cell {
59        Cell::new(' ', Color::Named(NamedColor::Foreground), Color::Named(NamedColor::Background))
60    }
61}
62
63impl GridCell for Cell {
64    #[inline]
65    fn is_empty(&self) -> bool {
66        (self.c == ' ' || self.c == '\t')
67            && self.extra[0] == ' '
68            && self.bg == Color::Named(NamedColor::Background)
69            && self.fg == Color::Named(NamedColor::Foreground)
70            && !self.flags.intersects(
71                Flags::INVERSE
72                    | Flags::UNDERLINE
73                    | Flags::STRIKEOUT
74                    | Flags::WRAPLINE
75                    | Flags::WIDE_CHAR_SPACER,
76            )
77    }
78
79    #[inline]
80    fn flags(&self) -> &Flags {
81        &self.flags
82    }
83
84    #[inline]
85    fn flags_mut(&mut self) -> &mut Flags {
86        &mut self.flags
87    }
88
89    #[inline]
90    fn fast_eq(&self, other: Self) -> bool {
91        self.bg == other.bg
92    }
93}
94
95/// Get the length of occupied cells in a line
96pub trait LineLength {
97    /// Calculate the occupied line length
98    fn line_length(&self) -> Column;
99}
100
101impl LineLength for grid::Row<Cell> {
102    fn line_length(&self) -> Column {
103        let mut length = Column(0);
104
105        if self[Column(self.len() - 1)].flags.contains(Flags::WRAPLINE) {
106            return Column(self.len());
107        }
108
109        for (index, cell) in self[..].iter().rev().enumerate() {
110            if cell.c != ' ' || cell.extra[0] != ' ' {
111                length = Column(self.len() - index);
112                break;
113            }
114        }
115
116        length
117    }
118}
119
120impl Cell {
121    #[inline]
122    pub fn bold(&self) -> bool {
123        self.flags.contains(Flags::BOLD)
124    }
125
126    #[inline]
127    pub fn inverse(&self) -> bool {
128        self.flags.contains(Flags::INVERSE)
129    }
130
131    #[inline]
132    pub fn dim(&self) -> bool {
133        self.flags.contains(Flags::DIM)
134    }
135
136    pub fn new(c: char, fg: Color, bg: Color) -> Cell {
137        Cell { extra: [' '; MAX_ZEROWIDTH_CHARS], c, bg, fg, flags: Flags::empty() }
138    }
139
140    #[inline]
141    pub fn reset(&mut self, template: &Cell) {
142        // memcpy template to self
143        *self = Cell { c: template.c, bg: template.bg, ..Cell::default() };
144    }
145
146    #[inline]
147    pub fn chars(&self) -> [char; MAX_ZEROWIDTH_CHARS + 1] {
148        unsafe {
149            let mut chars = [std::mem::MaybeUninit::uninit(); MAX_ZEROWIDTH_CHARS + 1];
150            std::ptr::write(chars[0].as_mut_ptr(), self.c);
151            std::ptr::copy_nonoverlapping(
152                self.extra.as_ptr() as *mut std::mem::MaybeUninit<char>,
153                chars.as_mut_ptr().offset(1),
154                self.extra.len(),
155            );
156            std::mem::transmute(chars)
157        }
158    }
159
160    #[inline]
161    pub fn push_extra(&mut self, c: char) {
162        for elem in self.extra.iter_mut() {
163            if elem == &' ' {
164                *elem = c;
165                break;
166            }
167        }
168    }
169}
170
171#[cfg(test)]
172mod tests {
173    use super::{Cell, LineLength};
174
175    use crate::grid::Row;
176    use crate::index::Column;
177
178    #[test]
179    fn line_length_works() {
180        let template = Cell::default();
181        let mut row = Row::new(Column(10), &template);
182        row[Column(5)].c = 'a';
183
184        assert_eq!(row.line_length(), Column(6));
185    }
186
187    #[test]
188    fn line_length_works_with_wrapline() {
189        let template = Cell::default();
190        let mut row = Row::new(Column(10), &template);
191        row[Column(9)].flags.insert(super::Flags::WRAPLINE);
192
193        assert_eq!(row.line_length(), Column(10));
194    }
195}
196
197#[cfg(all(test, feature = "bench"))]
198mod benches {
199    extern crate test;
200    use super::Cell;
201
202    #[bench]
203    fn cell_reset(b: &mut test::Bencher) {
204        b.iter(|| {
205            let mut cell = Cell::default();
206
207            for _ in 0..100 {
208                cell.reset(test::black_box(&Cell::default()));
209            }
210
211            test::black_box(cell);
212        });
213    }
214}