1use std::{borrow::Cow, cmp::max};
2
3use crate::{
4 records::vec_records::Cell,
5 util::string::{self, count_lines, get_lines, string_width},
6};
7
8/// The struct is a [Cell] implementation which keeps width information pre allocated.
9#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
10pub struct CellInfo<S> {
11 text: S,
12 width: usize,
13 lines: Vec<StrWithWidth<'static>>,
14}
15
16impl<S> CellInfo<S> {
17 /// Creates a new instance of the structure.
18 pub fn new(text: S) -> Self
19 where
20 S: AsRef<str>,
21 {
22 create_cell_info(text)
23 }
24
25 /// Creates a new instance of the structure with a single line.
26 pub fn exact(text: S, width: usize, lines: Vec<StrWithWidth<'static>>) -> Self {
27 Self { text, width, lines }
28 }
29
30 /// Return a original text value.
31 pub fn into_inner(self) -> S {
32 self.text
33 }
34}
35
36impl<S> AsRef<str> for CellInfo<S>
37where
38 S: AsRef<str>,
39{
40 fn as_ref(&self) -> &str {
41 self.text()
42 }
43}
44
45impl<S> Cell for CellInfo<S>
46where
47 S: AsRef<str>,
48{
49 fn text(&self) -> &str {
50 self.text.as_ref()
51 }
52
53 fn line(&self, i: usize) -> &str {
54 if i == 0 && self.lines.is_empty() {
55 return self.text.as_ref();
56 }
57
58 &self.lines[i].text
59 }
60
61 fn count_lines(&self) -> usize {
62 std::cmp::max(1, self.lines.len())
63 }
64
65 fn width(&self) -> usize {
66 self.width
67 }
68
69 fn line_width(&self, i: usize) -> usize {
70 if i == 0 && self.lines.is_empty() {
71 return self.width;
72 }
73
74 self.lines[i].width
75 }
76}
77
78impl<S> Clone for CellInfo<S>
79where
80 S: Clone + AsRef<str>,
81{
82 fn clone(&self) -> Self {
83 let mut cell = Self {
84 text: self.text.clone(),
85 width: self.width,
86 lines: vec![StrWithWidth::default(); self.lines.len()],
87 };
88
89 for (i, line) in self.lines.iter().enumerate() {
90 // We need to redirect pointers to the original string.
91 //
92 // # Safety
93 //
94 // It must be safe because the referenced string and the references are dropped at the same time.
95 // And the referenced String is guaranteed to not be changed.
96 let text = unsafe {
97 let text_ptr = self.text.as_ref().as_ptr();
98 let line_ptr = line.text.as_ptr();
99 let text_shift = line_ptr as isize - text_ptr as isize;
100
101 let new_text_shifted_ptr = cell.text.as_ref().as_ptr().offset(text_shift);
102
103 std::str::from_utf8_unchecked(std::slice::from_raw_parts(
104 new_text_shifted_ptr,
105 line.text.len(),
106 ))
107 };
108
109 cell.lines[i].width = line.width;
110 cell.lines[i].text = Cow::Borrowed(text);
111 }
112
113 cell
114 }
115}
116
117/// StrWithWidth is a structure is responsible for a string and it's width.
118#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Clone)]
119pub struct StrWithWidth<'a> {
120 text: Cow<'a, str>,
121 width: usize,
122}
123
124impl<'a> StrWithWidth<'a> {
125 /// Creates a new object.
126 pub fn new(text: Cow<'a, str>, width: usize) -> Self {
127 Self { text, width }
128 }
129}
130
131fn create_cell_info<S: AsRef<str>>(text: S) -> CellInfo<S> {
132 let mut info = CellInfo {
133 text,
134 lines: vec![],
135 width: 0,
136 };
137
138 // Here we do a small optimization.
139 // We check if there's only 1 line in which case we don't allocate lines Vec
140 let count_lines = count_lines(info.text.as_ref());
141 if count_lines < 2 {
142 info.width = string::string_width_multiline(info.text.as_ref());
143 return info;
144 }
145
146 // In case `Cow::Borrowed` we want to not allocate a String.
147 // It's currerently not possible due to a lifetime issues. (It's known as self-referential struct)
148 //
149 // Here we change the lifetime of text.
150 //
151 // # Safety
152 //
153 // It must be safe because the referenced string and the references are dropped at the same time.
154 // And the referenced String is guaranteed to not be changed.
155 let text = unsafe {
156 std::str::from_utf8_unchecked(std::slice::from_raw_parts(
157 info.text.as_ref().as_ptr(),
158 info.text.as_ref().len(),
159 ))
160 };
161
162 info.lines = vec![StrWithWidth::new(Cow::Borrowed(""), 0); count_lines];
163 for (line, i) in get_lines(text).zip(info.lines.iter_mut()) {
164 i.width = string_width(&line);
165 i.text = line;
166 info.width = max(info.width, i.width);
167 }
168
169 info
170}
171