1use std::ops::{Index, IndexMut};
2use std::rc::Rc;
3
4use pango;
5
6use super::cell::Cell;
7use super::item::Item;
8use crate::color;
9use crate::render;
10use crate::highlight::{HighlightMap, Highlight};
11
12pub struct Line {
13 pub line: Box<[Cell]>,
14
15 pub item_line: Box<[Option<Item>]>,
19 cell_to_item: Box<[i32]>,
20
21 pub dirty_line: bool,
22}
23
24impl Line {
25 pub fn new(columns: usize) -> Self {
26 Line {
27 line: vec![Cell::new_empty(); columns].into_boxed_slice(),
28 item_line: vec![None; columns].into_boxed_slice(),
29 cell_to_item: vec![-1; columns].into_boxed_slice(),
30 dirty_line: true,
31 }
32 }
33
34 pub fn swap_with(&mut self, target: &mut Self, left: usize, right: usize) {
35 target.line[left..=right].swap_with_slice(&mut self.line[left..=right]);
37
38 target.dirty_line = true;
40 for cell in &mut target.line[left..=right] {
41 cell.dirty = true;
42 }
43 }
44
45 pub fn clear(&mut self, left: usize, right: usize, default_hl: &Rc<Highlight>) {
46 for cell in &mut self.line[left..=right] {
47 cell.clear(default_hl.clone());
48 }
49 self.dirty_line = true;
50 }
51
52 pub fn clear_glyphs(&mut self) {
53 for i in 0..self.item_line.len() {
54 self.item_line[i] = None;
55 self.cell_to_item[i] = -1;
56 }
57 self.dirty_line = true;
58 }
59
60 fn set_cell_to_empty(&mut self, cell_idx: usize) -> bool {
61 if self.is_binded_to_item(cell_idx) {
62 self.item_line[cell_idx] = None;
63 self.cell_to_item[cell_idx] = -1;
64 self.line[cell_idx].dirty = true;
65 true
66 } else {
67 false
68 }
69 }
70
71 fn set_cell_to_item(&mut self, new_item: &PangoItemPosition) -> bool {
72 let start_item_idx = self.cell_to_item(new_item.start_cell);
73 let start_item_cells_count = if start_item_idx >= 0 {
74 self.item_line[start_item_idx as usize]
75 .as_ref()
76 .map_or(-1, |item| item.cells_count as i32)
77 } else {
78 -1
79 };
80
81 let end_item_idx = self.cell_to_item(new_item.end_cell);
82
83 if start_item_idx != new_item.start_cell as i32
87 || new_item.cells_count() != start_item_cells_count
88 || start_item_idx == -1
89 || end_item_idx == -1
90 {
91 self.initialize_cell_item(new_item.start_cell, new_item.end_cell, new_item.item);
92 true
93 } else {
94 if self.line[new_item.start_cell..=new_item.end_cell]
96 .iter()
97 .any(|c| c.dirty)
98 {
99 self.item_line[new_item.start_cell]
100 .as_mut()
101 .unwrap()
102 .update(new_item.item.clone());
103 self.line[new_item.start_cell].dirty = true;
104 true
105 } else {
106 false
107 }
108 }
109 }
110
111 pub fn merge(&mut self, old_items: &StyledLine, pango_items: &[pango::Item]) {
112 let mut pango_item_iter = pango_items
113 .iter()
114 .map(|item| PangoItemPosition::new(old_items, item));
115
116 let mut next_item = pango_item_iter.next();
117 let mut move_to_next_item = false;
118
119 let mut cell_idx = 0;
120 while cell_idx < self.line.len() {
121 let dirty = match next_item {
122 None => self.set_cell_to_empty(cell_idx),
123 Some(ref new_item) => {
124 if cell_idx < new_item.start_cell {
125 self.set_cell_to_empty(cell_idx)
126 } else if cell_idx == new_item.start_cell {
127 move_to_next_item = true;
128 self.set_cell_to_item(new_item)
129 } else {
130 false
131 }
132 }
133 };
134
135 self.dirty_line = self.dirty_line || dirty;
136 if move_to_next_item {
137 let new_item = next_item.unwrap();
138 cell_idx += new_item.end_cell - new_item.start_cell + 1;
139 next_item = pango_item_iter.next();
140 move_to_next_item = false;
141 } else {
142 cell_idx += 1;
143 }
144 }
145 }
146
147 fn initialize_cell_item(
148 &mut self,
149 start_cell: usize,
150 end_cell: usize,
151 new_item: &pango::Item,
152 ) {
153 for i in start_cell..=end_cell {
154 self.line[i].dirty = true;
155 self.cell_to_item[i] = start_cell as i32;
156 }
157 for i in start_cell + 1..=end_cell {
158 self.item_line[i] = None;
159 }
160 self.item_line[start_cell] = Some(Item::new(new_item.clone(), end_cell - start_cell + 1));
161 }
162
163 pub fn get_item(&self, cell_idx: usize) -> Option<&Item> {
164 let item_idx = self.cell_to_item(cell_idx);
165 if item_idx >= 0 {
166 self.item_line[item_idx as usize].as_ref()
167 } else {
168 None
169 }
170 }
171
172 #[inline]
173 pub fn cell_to_item(&self, cell_idx: usize) -> i32 {
174 self.cell_to_item[cell_idx]
175 }
176
177 pub fn item_len_from_idx(&self, start_idx: usize) -> usize {
178 debug_assert!(
179 start_idx < self.line.len(),
180 "idx={}, len={}",
181 start_idx,
182 self.line.len()
183 );
184
185 let item_idx = self.cell_to_item(start_idx);
186
187 if item_idx >= 0 {
188 let item_idx = item_idx as usize;
189 let cells_count = self.item_line[item_idx].as_ref().unwrap().cells_count;
190 let offset = start_idx - item_idx;
191
192 cells_count - offset
193 } else {
194 1
195 }
196 }
197
198 #[inline]
199 pub fn is_binded_to_item(&self, cell_idx: usize) -> bool {
200 self.cell_to_item[cell_idx] >= 0
201 }
202}
203
204impl Index<usize> for Line {
205 type Output = Cell;
206
207 fn index(&self, index: usize) -> &Cell {
208 &self.line[index]
209 }
210}
211
212impl IndexMut<usize> for Line {
213 fn index_mut(&mut self, index: usize) -> &mut Cell {
214 &mut self.line[index]
215 }
216}
217
218struct PangoItemPosition<'a> {
219 item: &'a pango::Item,
220 start_cell: usize,
221 end_cell: usize,
222}
223
224impl<'a> PangoItemPosition<'a> {
225 pub fn new(styled_line: &StyledLine, item: &'a pango::Item) -> Self {
226 let offset = item.offset() as usize;
227 let length = item.length() as usize;
228 let start_cell = styled_line.cell_to_byte[offset];
229 let end_cell = styled_line.cell_to_byte[offset + length - 1];
230
231 PangoItemPosition {
232 item,
233 start_cell,
234 end_cell,
235 }
236 }
237
238 #[inline]
239 fn cells_count(&self) -> i32 {
240 (self.end_cell - self.start_cell) as i32 + 1
241 }
242}
243
244pub struct StyledLine {
245 pub line_str: String,
246 cell_to_byte: Box<[usize]>,
247 pub attr_list: pango::AttrList,
248}
249
250impl StyledLine {
251 pub fn from(
252 line: &Line,
253 hl: &HighlightMap,
254 font_features: &render::FontFeatures,
255 ) -> Self {
256 let average_capacity = line.line.len() * 4 * 2; let mut line_str = String::with_capacity(average_capacity);
259 let mut cell_to_byte = Vec::with_capacity(average_capacity);
260 let attr_list = pango::AttrList::new();
261 let mut byte_offset = 0;
262 let mut style_attr = StyleAttr::new();
263
264 for (cell_idx, cell) in line.line.iter().enumerate() {
265 if cell.double_width {
266 continue;
267 }
268
269 if !cell.ch.is_empty() {
270 line_str.push_str(&cell.ch);
271 } else {
272 line_str.push(' ');
273 }
274 let len = line_str.len() - byte_offset;
275
276 for _ in 0..len {
277 cell_to_byte.push(cell_idx);
278 }
279
280 let next = style_attr.next(byte_offset, byte_offset + len, cell, hl);
281 if let Some(next) = next {
282 style_attr.insert_into(&attr_list);
283 style_attr = next;
284 }
285
286 byte_offset += len;
287 }
288
289 style_attr.insert_into(&attr_list);
290 font_features.insert_into(&attr_list);
291
292 StyledLine {
293 line_str,
294 cell_to_byte: cell_to_byte.into_boxed_slice(),
295 attr_list,
296 }
297 }
298}
299
300struct StyleAttr<'c> {
301 italic: bool,
302 bold: bool,
303 foreground: Option<&'c color::Color>,
304 background: Option<&'c color::Color>,
305 empty: bool,
306 space: bool,
307
308 start_idx: usize,
309 end_idx: usize,
310}
311
312impl<'c> StyleAttr<'c> {
313 fn new() -> Self {
314 StyleAttr {
315 italic: false,
316 bold: false,
317 foreground: None,
318 background: None,
319 empty: true,
320 space: false,
321
322 start_idx: 0,
323 end_idx: 0,
324 }
325 }
326
327 fn from(
328 start_idx: usize,
329 end_idx: usize,
330 cell: &'c Cell,
331 hl: &'c HighlightMap,
332 ) -> Self {
333 StyleAttr {
334 italic: cell.hl.italic,
335 bold: cell.hl.bold,
336 foreground: hl.cell_fg(cell),
337 background: hl.cell_bg(cell),
338 empty: false,
339 space: cell.ch.is_empty(),
340
341 start_idx,
342 end_idx,
343 }
344 }
345
346 fn next(
347 &mut self,
348 start_idx: usize,
349 end_idx: usize,
350 cell: &'c Cell,
351 hl: &'c HighlightMap,
352 ) -> Option<StyleAttr<'c>> {
353 if self.space && cell.ch.is_empty() {
355 self.end_idx = end_idx;
356 return None;
357 }
358
359
360 let style_attr = Self::from(start_idx, end_idx, cell, hl);
361
362 if self != &style_attr {
363 Some(style_attr)
364 } else {
365 self.end_idx = end_idx;
366 None
367 }
368 }
369
370 fn insert_into(&self, attr_list: &pango::AttrList) {
371 if self.empty {
372 return;
373 }
374
375 if self.italic {
376 self.insert_attr(
377 attr_list,
378 pango::Attribute::new_style(pango::Style::Italic).unwrap(),
379 );
380 }
381
382 if self.bold {
383 self.insert_attr(
384 attr_list,
385 pango::Attribute::new_weight(pango::Weight::Bold).unwrap(),
386 );
387 }
388
389 if let Some(fg) = self.foreground {
390 let (r, g, b) = fg.to_u16();
391 self.insert_attr(
392 attr_list,
393 pango::Attribute::new_foreground(r, g, b).unwrap(),
394 );
395 }
396
397 if let Some(bg) = self.background {
398 let (r, g, b) = bg.to_u16();
399 self.insert_attr(
400 attr_list,
401 pango::Attribute::new_background(r, g, b).unwrap(),
402 );
403 }
404 }
405
406 #[inline]
407 fn insert_attr(&self, attr_list: &pango::AttrList, mut attr: pango::Attribute) {
408 attr.set_start_index(self.start_idx as u32);
409 attr.set_end_index(self.end_idx as u32);
410 attr_list.insert(attr);
411 }
412}
413
414impl<'c> PartialEq for StyleAttr<'c> {
415 fn eq(&self, other: &Self) -> bool {
416 self.italic == other.italic
417 && self.bold == other.bold
418 && self.foreground == other.foreground
419 && self.empty == other.empty
420 && self.background == other.background
421 }
422}
423
424#[cfg(test)]
425mod tests {
426 use super::*;
427
428 #[test]
429 fn test_styled_line() {
430 let mut line = Line::new(3);
431 line[0].ch = "a".to_owned();
432 line[1].ch = "b".to_owned();
433 line[2].ch = "c".to_owned();
434
435 let styled_line = StyledLine::from(
436 &line,
437 &HighlightMap::new(),
438 &render::FontFeatures::new(),
439 );
440 assert_eq!("abc", styled_line.line_str);
441 assert_eq!(3, styled_line.cell_to_byte.len());
442 assert_eq!(0, styled_line.cell_to_byte[0]);
443 assert_eq!(1, styled_line.cell_to_byte[1]);
444 assert_eq!(2, styled_line.cell_to_byte[2]);
445 }
446}