papergrid/records/
cell_info.rs

1//! A [`Cell`] implementation for [`VecRecords`].
2//!
3//! [`VecRecords`]: crate::records::vec_records::VecRecords
4
5use std::{borrow::Cow, cmp::max};
6
7use crate::{
8    records::vec_records::{Cell, CellMut},
9    util::{count_lines, get_lines},
10    width::WidthFunc,
11};
12
13/// The struct is a [Cell] implementation which keeps width information pre allocated.
14#[derive(Debug, Default)]
15pub struct CellInfo<'a> {
16    text: Cow<'a, str>,
17    width: usize,
18    lines: Vec<StrWithWidth<'a>>,
19    count_lines: usize,
20}
21
22impl<'a> CellInfo<'a> {
23    /// Creates a new instance of the structure.
24    pub fn new<S, W>(text: S, width_ctrl: W) -> Self
25    where
26        S: Into<Cow<'a, str>>,
27        W: WidthFunc,
28    {
29        create_cell_info(text.into(), width_ctrl)
30    }
31
32    /// Checks if the containing string is empty.
33    pub fn is_empty(&self) -> bool {
34        self.text.is_empty()
35    }
36}
37
38impl Cell for CellInfo<'_> {
39    fn get_line(&self, i: usize) -> &str {
40        if i == 0 && self.lines.is_empty() {
41            return &self.text;
42        }
43
44        &self.lines[i].text
45    }
46
47    fn count_lines(&self) -> usize {
48        self.count_lines
49    }
50
51    fn width<W>(&self, _: W) -> usize
52    where
53        W: WidthFunc,
54    {
55        self.width
56    }
57
58    fn line_width<W>(&self, i: usize, _: W) -> usize
59    where
60        W: WidthFunc,
61    {
62        if i == 0 && self.lines.is_empty() {
63            return self.width;
64        }
65
66        self.lines[i].width
67    }
68}
69
70impl<'a, T> CellMut<T> for CellInfo<'a>
71where
72    T: Into<Cow<'a, str>>,
73{
74    fn update<W>(&mut self, width_ctrl: W)
75    where
76        W: WidthFunc,
77    {
78        self.width = 0;
79        update_cell_info(self, width_ctrl);
80    }
81
82    fn set<W>(&mut self, text: T, width_ctrl: W)
83    where
84        W: WidthFunc,
85    {
86        let text = text.into();
87        *self = create_cell_info(text, width_ctrl);
88    }
89}
90
91impl AsRef<str> for CellInfo<'_> {
92    fn as_ref(&self) -> &str {
93        &self.text
94    }
95}
96
97impl Clone for CellInfo<'_> {
98    fn clone(&self) -> Self {
99        let mut cell = Self {
100            text: self.text.clone(),
101            width: self.width,
102            lines: vec![StrWithWidth::default(); self.lines.len()],
103            count_lines: self.count_lines,
104        };
105
106        for (i, line) in self.lines.iter().enumerate() {
107            cell.lines[i].width = line.width;
108
109            cell.lines[i].text = match &line.text {
110                Cow::Owned(line) => Cow::Owned(line.clone()),
111                Cow::Borrowed(s) => {
112                    // We need to redirect pointers to the original string.
113                    //
114                    // # Safety
115                    //
116                    // It must be safe because the referenced string and the references are dropped at the same time.
117                    // And the referenced String is guaranted to not be changed.
118                    let text = unsafe {
119                        let text_ptr = self.text.as_ptr();
120                        let line_ptr = s.as_ptr();
121                        let text_shift = line_ptr as isize - text_ptr as isize;
122
123                        let new_text_shifted_ptr = cell.text.as_ptr().offset(text_shift);
124
125                        std::str::from_utf8_unchecked(std::slice::from_raw_parts(
126                            new_text_shifted_ptr,
127                            s.len(),
128                        ))
129                    };
130
131                    Cow::Borrowed(text)
132                }
133            }
134        }
135
136        cell
137    }
138}
139
140#[derive(Debug, Clone, Default)]
141struct StrWithWidth<'a> {
142    text: Cow<'a, str>,
143    width: usize,
144}
145
146impl<'a> StrWithWidth<'a> {
147    fn new(text: Cow<'a, str>, width: usize) -> Self {
148        Self { text, width }
149    }
150}
151
152fn create_cell_info<W>(text: Cow<'_, str>, width_fn: W) -> CellInfo<'_>
153where
154    W: WidthFunc,
155{
156    let mut info = CellInfo {
157        text,
158        count_lines: 1,
159        ..Default::default()
160    };
161
162    // Here we do a small optimization.
163    // We check if there's only 1 line in which case we don't allocate lines Vec
164    let count_lines = count_lines(&info.text);
165    if count_lines < 2 {
166        info.width = width_fn.width(&info.text);
167        return info;
168    }
169
170    // In case `Cow::Borrowed` we want to not allocate a String.
171    // It's currerently not possible due to a lifetime issues. (It's known as self-referential struct)
172    //
173    // Here we change the lifetime of text.
174    //
175    // # Safety
176    //
177    // It must be safe because the referenced string and the references are dropped at the same time.
178    // And the referenced String is guaranted to not be changed.
179    let text = unsafe {
180        std::str::from_utf8_unchecked(std::slice::from_raw_parts(
181            info.text.as_ptr(),
182            info.text.len(),
183        ))
184    };
185
186    info.count_lines = count_lines;
187    info.lines = vec![StrWithWidth::new(Cow::Borrowed(""), 0); count_lines];
188    for (line, i) in get_lines(text).zip(info.lines.iter_mut()) {
189        i.width = width_fn.width(line.as_ref());
190        i.text = line;
191        info.width = max(info.width, i.width);
192    }
193
194    info
195}
196
197fn update_cell_info<W>(info: &mut CellInfo<'_>, width_fn: W)
198where
199    W: WidthFunc,
200{
201    if info.text.is_empty() {
202        return;
203    }
204
205    if info.lines.is_empty() && !info.text.is_empty() {
206        info.width = width_fn.width(&info.text);
207        return;
208    }
209
210    for line in &mut info.lines {
211        line.width = width_fn.width(&line.text);
212        info.width = max(info.width, line.width);
213    }
214}