cursive_cached_text_view/
lib.rs

1use std::ops::Deref;
2use std::sync::{Mutex, MutexGuard};
3use std::sync::Arc;
4
5use cursive::{Printer, Vec2, With, XY};
6use cursive::align::*;
7use cursive::theme::StyleType;
8use cursive::utils::lines::spans::{LinesIterator, Row};
9use cursive::utils::markup::StyledString;
10use cursive::view::{SizeCache, View};
11use owning_ref::{ArcRef, OwningHandle};
12use unicode_width::UnicodeWidthStr;
13
14// Content type used internally for caching and storage
15type InnerContentType = Arc<StyledString>;
16
17/// Provides access to the content of a [`TextView`].
18///
19/// [`TextView`]: struct.TextView.html
20///
21/// Cloning this object will still point to the same content.
22///
23/// # Examples
24///
25/// ```rust
26/// # use cursive::views::{TextView, TextContent};
27/// let mut content = TextContent::new("content");
28/// let view = TextView::new_with_content(content.clone());
29///
30/// // Later, possibly in a different thread
31/// content.set_content("new content");
32/// assert!(view.get_content().source().contains("new"));
33/// ```
34#[derive(Clone)]
35pub struct TextContent {
36    content: Arc<Mutex<TextContentInner>>,
37}
38
39impl TextContent {
40    /// Creates a new text content around the given value.
41    ///
42    /// Parses the given value.
43    pub fn new<S>(content: S) -> Self
44        where
45            S: Into<StyledString>,
46    {
47        let content = Arc::new(content.into());
48
49        TextContent {
50            content: Arc::new(Mutex::new(TextContentInner {
51                content_value: content,
52                content_cache: Arc::new(StyledString::default()),
53                size_cache: None,
54            })),
55        }
56    }
57}
58
59/// A reference to the text content.
60///
61/// This can be deref'ed into a [`StyledString`].
62///
63/// [`StyledString`]: ../utils/markup/type.StyledString.html
64///
65/// This keeps the content locked. Do not store this!
66pub struct TextContentRef {
67    _handle: OwningHandle<
68        ArcRef<Mutex<TextContentInner>>,
69        MutexGuard<'static, TextContentInner>,
70    >,
71    // We also need to keep a copy of Arc so `deref` can return
72    // a reference to the `StyledString`
73    data: Arc<StyledString>,
74}
75
76impl Deref for TextContentRef {
77    type Target = StyledString;
78
79    fn deref(&self) -> &StyledString {
80        self.data.as_ref()
81    }
82}
83
84impl TextContent {
85    /// Replaces the content with the given value.
86    pub fn set_content<S>(&self, content: S)
87        where
88            S: Into<StyledString>,
89    {
90        self.with_content(|c| {
91            *c = content.into();
92        });
93    }
94
95    /// Append `content` to the end of a `TextView`.
96    pub fn append<S>(&self, content: S)
97        where
98            S: Into<StyledString>,
99    {
100        self.with_content(|c| {
101            // This will only clone content if content_cached and content_value
102            // are sharing the same underlying Rc.
103            c.append(content);
104        })
105    }
106
107    /// Returns a reference to the content.
108    ///
109    /// This locks the data while the returned value is alive,
110    /// so don't keep it too long.
111    pub fn get_content(&self) -> TextContentRef {
112        TextContentInner::get_content(&self.content)
113    }
114
115    /// Apply the given closure to the inner content, and bust the cache afterward.
116    pub fn with_content<F, O>(&self, f: F) -> O
117        where
118            F: FnOnce(&mut StyledString) -> O,
119    {
120        self.with_content_inner(|c| f(Arc::make_mut(&mut c.content_value)))
121    }
122
123    /// Apply the given closure to the inner content, and bust the cache afterward.
124    fn with_content_inner<F, O>(&self, f: F) -> O
125        where
126            F: FnOnce(&mut TextContentInner) -> O,
127    {
128        let mut content = self.content.lock().unwrap();
129
130        let out = f(&mut content);
131
132        content.size_cache = None;
133
134        out
135    }
136}
137
138/// Internel representation of the content for a `TextView`.
139///
140/// This is mostly just a `StyledString`.
141///
142/// Can be shared (through a `Arc<Mutex>`).
143struct TextContentInner {
144    // content: String,
145    content_value: InnerContentType,
146    content_cache: InnerContentType,
147
148    // We keep the cache here so it can be busted when we change the content.
149    size_cache: Option<XY<SizeCache>>,
150}
151
152impl TextContentInner {
153    /// From a shareable content (Arc + Mutex), return a
154    fn get_content(content: &Arc<Mutex<TextContentInner>>) -> TextContentRef {
155        let arc_ref: ArcRef<Mutex<TextContentInner>> =
156            ArcRef::new(Arc::clone(content));
157
158        let _handle = OwningHandle::new_with_fn(arc_ref, |mutex| unsafe {
159            (*mutex).lock().unwrap()
160        });
161
162        let data = Arc::clone(&_handle.content_value);
163
164        TextContentRef { _handle, data }
165    }
166
167    fn is_cache_valid(&self, size: Vec2) -> bool {
168        match self.size_cache {
169            None => false,
170            Some(ref last) => last.x.accept(size.x) && last.y.accept(size.y),
171        }
172    }
173
174    fn get_cache(&self) -> &InnerContentType {
175        &self.content_cache
176    }
177}
178
179/// A simple view showing a fixed text.
180///
181/// # Examples
182///
183/// ```rust
184/// use cursive::Cursive;
185/// use cursive_cached_text_view::CachedTextView;
186/// let mut siv = Cursive::new();
187///
188/// siv.add_layer(CachedTextView::new("Hello world!", 5));
189/// ```
190pub struct CachedTextView {
191    cache: TinyCache<usize, Vec<Row>>,
192    content: TextContent,
193
194    align: Align,
195
196    style: StyleType,
197
198    // True if we can wrap long lines.
199    wrap: bool,
200
201    // ScrollBase make many scrolling-related things easier
202    width: Option<usize>,
203}
204
205impl CachedTextView {
206    /// Creates a new TextView with the given content.
207    pub fn new<S>(content: S, cache_size: usize) -> Self
208        where
209            S: Into<StyledString>,
210    {
211        Self::new_with_content(TextContent::new(content), cache_size)
212    }
213
214    /// Creates a new TextView using the given `TextContent`.
215    ///
216    /// If you kept a clone of the given content, you'll be able to update it
217    /// remotely.
218    ///
219    /// # Examples
220    ///
221    /// ```rust
222    /// # use cursive_cached_text_view::{TextContent, CachedTextView};
223    /// let mut content = TextContent::new("content");
224    /// let view = CachedTextView::new_with_content(content.clone(), 5);
225    ///
226    /// // Later, possibly in a different thread
227    /// content.set_content("new content");
228    /// assert!(view.get_content().source().contains("new"));
229    /// ```
230    pub fn new_with_content(content: TextContent, cache_size: usize) -> Self {
231        CachedTextView {
232            cache: TinyCache::new(cache_size),
233            content,
234            style: StyleType::default(),
235            wrap: true,
236            align: Align::top_left(),
237            width: None,
238        }
239    }
240
241    /// Creates a new empty `TextView`.
242    pub fn empty() -> Self {
243        CachedTextView::new("", 5)
244    }
245
246    /// Sets the style for the content.
247    pub fn set_style<S: Into<StyleType>>(&mut self, style: S) {
248        self.cache.clear();
249        self.style = style.into();
250    }
251
252    /// Sets the style for the entire content.
253    ///
254    /// Chainable variant.
255    #[must_use]
256    pub fn style<S: Into<StyleType>>(self, style: S) -> Self {
257        self.with(|s| s.set_style(style))
258    }
259
260    /// Disables content wrap for this view.
261    ///
262    /// This may be useful if you want horizontal scrolling.
263    #[must_use]
264    pub fn no_wrap(self) -> Self {
265        self.with(|s| s.set_content_wrap(false))
266    }
267
268    /// Controls content wrap for this view.
269    ///
270    /// If `true` (the default), text will wrap long lines when needed.
271    pub fn set_content_wrap(&mut self, wrap: bool) {
272        self.cache.clear();
273        self.wrap = wrap;
274    }
275
276    /// Sets the horizontal alignment for this view.
277    #[must_use]
278    pub fn h_align(mut self, h: HAlign) -> Self {
279        self.align.h = h;
280
281        self
282    }
283
284    /// Sets the vertical alignment for this view.
285    #[must_use]
286    pub fn v_align(mut self, v: VAlign) -> Self {
287        self.align.v = v;
288
289        self
290    }
291
292    /// Sets the alignment for this view.
293    #[must_use]
294    pub fn align(mut self, a: Align) -> Self {
295        self.align = a;
296
297        self
298    }
299
300    /// Center the text horizontally and vertically inside the view.
301    #[must_use]
302    pub fn center(mut self) -> Self {
303        self.align = Align::center();
304        self
305    }
306
307    /// Replace the text in this view.
308    ///
309    /// Chainable variant.
310    #[must_use]
311    pub fn content<S>(self, content: S) -> Self
312        where
313            S: Into<StyledString>,
314    {
315        self.with(|s| s.set_content(content))
316    }
317
318    /// Replace the text in this view.
319    pub fn set_content<S>(&mut self, content: S)
320        where
321            S: Into<StyledString>,
322    {
323        self.cache.clear();
324        self.content.set_content(content);
325    }
326
327    /// Append `content` to the end of a `TextView`.
328    pub fn append<S>(&mut self, content: S)
329        where
330            S: Into<StyledString>,
331    {
332        self.cache.clear();
333        self.content.append(content);
334    }
335
336    /// Returns the current text in this view.
337    pub fn get_content(&self) -> TextContentRef {
338        TextContentInner::get_content(&self.content.content)
339    }
340
341    /// Returns a shared reference to the content, allowing content mutation.
342    pub fn get_shared_content(&mut self) -> TextContent {
343        // We take &mut here without really needing it,
344        // because it sort of "makes sense".
345        TextContent {
346            content: Arc::clone(&self.content.content),
347        }
348    }
349
350    // This must be non-destructive, as it may be called
351    // multiple times during layout.
352    fn compute_rows(&mut self, size: Vec2) {
353        let size = if self.wrap { size } else { Vec2::max_value() };
354
355        let mut content = self.content.content.lock().unwrap();
356        if content.is_cache_valid(size) {
357            return;
358        }
359
360        // Completely bust the cache
361        // Just in case we fail, we don't want to leave a bad cache.
362        content.size_cache = None;
363        content.content_cache = Arc::clone(&content.content_value);
364
365        let rows = self.cache.compute(size.x, || {
366            LinesIterator::new(content.get_cache().as_ref(), size.x).collect()
367        });
368
369        // Desired width
370        self.width = if rows.iter().any(|row| row.is_wrapped) {
371            // If any rows are wrapped, then require the full width.
372            Some(size.x)
373        } else {
374            rows.iter().map(|row| row.width).max()
375        }
376    }
377}
378
379impl View for CachedTextView {
380    fn draw(&self, printer: &Printer) {
381        let rows = if let Some(rows) = self.cache.last() {
382            rows
383        } else {
384            return;
385        };
386        let h = rows.len();
387        // If the content is smaller than the view, align it somewhere.
388        let offset = self.align.v.get_offset(h, printer.size.y);
389        let printer = &printer.offset((0, offset));
390
391        let content = self.content.content.lock().unwrap();
392
393        printer.with_style(self.style, |printer| {
394            for (y, row) in rows
395                .iter()
396                .enumerate()
397                .skip(printer.content_offset.y)
398                .take(printer.output_size.y)
399            {
400                let l = row.width;
401                let mut x = self.align.h.get_offset(l, printer.size.x);
402
403                for span in row.resolve_stream(content.get_cache().as_ref()) {
404                    printer.with_style(*span.attr, |printer| {
405                        printer.print((x, y), span.content);
406                        x += span.content.width();
407                    });
408                }
409            }
410        });
411    }
412
413    fn layout(&mut self, size: Vec2) {
414        // Compute the text rows.
415        self.compute_rows(size);
416
417        let num_rows = self.cache.last().map(|rows| rows.len()).unwrap_or(0);
418
419        // The entire "virtual" size (includes all rows)
420        let my_size = Vec2::new(self.width.unwrap_or(0), num_rows);
421
422        // Build a fresh cache.
423        let mut content = self.content.content.lock().unwrap();
424        content.size_cache = Some(SizeCache::build(my_size, size));
425    }
426
427    fn needs_relayout(&self) -> bool {
428        let content = self.content.content.lock().unwrap();
429        content.size_cache.is_none()
430    }
431
432    fn required_size(&mut self, size: Vec2) -> Vec2 {
433        self.compute_rows(size);
434
435        let num_rows = self.cache.last().map(|rows| rows.len()).unwrap_or(0);
436
437        Vec2::new(self.width.unwrap_or(0), num_rows)
438    }
439}
440
441
442struct TinyCache<K, V> {
443    size: usize,
444    data: Vec<(usize, K, V)>,
445}
446
447impl<K, V> TinyCache<K, V> {
448    fn new(size: usize) -> Self {
449        TinyCache {
450            size,
451            data: Vec::with_capacity(size),
452        }
453    }
454
455    fn get_key_index(&self, key: &K) -> Option<usize> where K: Eq {
456        self.data.iter().rposition(|(_, k, _)| k == key)
457    }
458
459    fn compute(&mut self, key: K, f: impl FnOnce() -> V) -> &V where K: Eq {
460        if let Some(index) = self.get_key_index(&key) {
461            self.data[index].0 += 1;
462            return &self.data[index].2;
463        }
464
465        let v = f();
466        self.clean();
467        self.data.push((0, key, v));
468        &self.data.last().as_ref().unwrap().2
469    }
470
471    fn clean(&mut self) {
472        if self.data.len() < self.size {
473            return;
474        }
475        let index = self.data.iter().enumerate()
476            .min_by_key(|(_, (count, _, _))| *count)
477            .map(|(i, _)| i);
478
479        if let Some(index) = index {
480            self.data.swap_remove(index);
481        }
482    }
483
484    fn clear(&mut self) {
485        self.data.clear();
486    }
487
488    fn last(&self) -> Option<&V> {
489        self.data.last().map(|(_, _, v)| v)
490    }
491
492    #[cfg(test)]
493    fn len(&self) -> usize {
494        self.data.len()
495    }
496
497    #[cfg(test)]
498    fn is_empty(&self) -> bool {
499        self.data.is_empty()
500    }
501
502    #[cfg(test)]
503    fn keys(&self) -> Vec<(&K, usize)> {
504        self.data.iter().map(|(count, key, _)| (key, *count)).collect()
505    }
506}
507
508#[cfg(test)]
509mod tests {
510    use cursive::theme::Style;
511    use cursive::Vec2;
512
513    use super::*;
514
515    #[test]
516    fn sanity() {
517        let text_view = CachedTextView::new("", 5);
518        assert_eq!(text_view.get_content().data.spans().len(), 1);
519    }
520
521    #[test]
522    fn test_cache() {
523        let mut text_view = CachedTextView::new("sample", 3);
524        assert!(text_view.cache.is_empty());
525
526        text_view.compute_rows(Vec2::new(0, 0));
527        assert_eq!(text_view.cache.len(), 1);
528        text_view.compute_rows(Vec2::new(0, 0));
529        assert_eq!(text_view.cache.len(), 1);
530
531        text_view.compute_rows(Vec2::new(1, 0));
532        assert_eq!(text_view.cache.len(), 2);
533
534        text_view.compute_rows(Vec2::new(2, 0));
535        assert_eq!(text_view.cache.len(), 3);
536
537        text_view.compute_rows(Vec2::new(3, 0));
538        assert_eq!(text_view.cache.len(), 3);
539
540        assert_eq!(text_view.cache.keys(), [(&0, 1), (&2, 0), (&3, 0)]);
541
542        text_view.set_content("");
543        assert_eq!(text_view.cache.len(), 0);
544        text_view.compute_rows(Vec2::new(0, 0));
545
546        text_view.append("sample");
547        assert_eq!(text_view.cache.len(), 0);
548        text_view.compute_rows(Vec2::new(0, 0));
549
550        text_view.set_content_wrap(false);
551        assert_eq!(text_view.cache.len(), 0);
552        text_view.compute_rows(Vec2::new(0, 0));
553
554        text_view.set_style(Style::view());
555        assert_eq!(text_view.cache.len(), 0);
556    }
557}