cap scrollback

This commit is contained in:
Christien Rioux 2023-12-09 16:47:43 -05:00
parent 4acb760c10
commit b791a82ec6
3 changed files with 133 additions and 62 deletions

View File

@ -1,3 +1,4 @@
use std::collections::VecDeque;
use std::ops::Deref; use std::ops::Deref;
use std::sync::Arc; use std::sync::Arc;
use std::sync::{Mutex, MutexGuard}; use std::sync::{Mutex, MutexGuard};
@ -12,7 +13,32 @@ use owning_ref::{ArcRef, OwningHandle};
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
// Content type used internally for caching and storage // Content type used internally for caching and storage
type InnerContentType = Arc<StyledString>; type ContentType = VecDeque<StyledString>;
type InnerContentType = Arc<ContentType>;
type CacheType = StyledString;
type InnerCacheType = Arc<StyledString>;
/// A reference to the text content.
///
/// This can be deref'ed into a [`StyledString`].
///
/// [`StyledString`]: ../utils/markup/type.StyledString.html
///
/// This keeps the content locked. Do not store this!
pub struct TextContentRef {
_handle: OwningHandle<ArcRef<Mutex<TextContentInner>>, MutexGuard<'static, TextContentInner>>,
// We also need to keep a copy of Arc so `deref` can return
// a reference to the `StyledString`
data: Arc<VecDeque<StyledString>>,
}
impl Deref for TextContentRef {
type Target = VecDeque<StyledString>;
fn deref(&self) -> &VecDeque<StyledString> {
self.data.as_ref()
}
}
/// Provides access to the content of a [`TextView`]. /// Provides access to the content of a [`TextView`].
/// ///
@ -36,69 +62,75 @@ pub struct TextContent {
content: Arc<Mutex<TextContentInner>>, content: Arc<Mutex<TextContentInner>>,
} }
#[allow(dead_code)]
impl TextContent { impl TextContent {
/// Creates a new text content around the given value. /// Creates a new text content around the given value.
/// ///
/// Parses the given value. /// Parses the given value.
pub fn new<S>(content: S) -> Self pub fn new<S>(content: S) -> Self
where where
S: Into<StyledString>, S: Into<ContentType>,
{ {
let content = Arc::new(content.into()); let content = Arc::new(content.into());
TextContent { TextContent {
content: Arc::new(Mutex::new(TextContentInner { content: Arc::new(Mutex::new(TextContentInner {
content_value: content, content_value: content,
content_cache: Arc::new(StyledString::default()), content_cache: Arc::new(CacheType::default()),
size_cache: None, size_cache: None,
})), })),
} }
} }
}
/// A reference to the text content.
///
/// This can be deref'ed into a [`StyledString`].
///
/// [`StyledString`]: ../utils/markup/type.StyledString.html
///
/// This keeps the content locked. Do not store this!
pub struct TextContentRef {
_handle: OwningHandle<ArcRef<Mutex<TextContentInner>>, MutexGuard<'static, TextContentInner>>,
// We also need to keep a copy of Arc so `deref` can return
// a reference to the `StyledString`
data: Arc<StyledString>,
}
impl Deref for TextContentRef {
type Target = StyledString;
fn deref(&self) -> &StyledString {
self.data.as_ref()
}
}
#[allow(dead_code)]
impl TextContent {
/// Replaces the content with the given value. /// Replaces the content with the given value.
pub fn set_content<S>(&self, content: S) pub fn set_content<S>(&self, content: S)
where where
S: Into<StyledString>, S: Into<ContentType>,
{ {
self.with_content(|c| { self.with_content(|c| {
*c = content.into(); *c = content.into();
}); });
} }
/// Append `content` to the end of a `TextView`. /// Append `line` to the end of a `TextView`.
pub fn append<S>(&self, content: S) pub fn append_line<S>(&self, line: S)
where where
S: Into<StyledString>, S: Into<StyledString>,
{ {
self.with_content(|c| { self.with_content(|c| {
// This will only clone content if content_cached and content_value c.push_back(line.into());
// are sharing the same underlying Rc. })
c.append(content); }
/// Append `lines` to the end of a `TextView`.
pub fn append_lines<I, S>(&self, lines: S)
where
S: Iterator<Item = I>,
I: Into<StyledString>,
{
self.with_content(|c| {
for line in lines {
c.push_back(line.into());
}
})
}
/// Remove lines from the beginning until we have no more than 'count' from the end
pub fn resize_back(&self, count: usize) {
self.with_content(|c| {
while c.len() > count {
c.remove(0);
}
})
}
/// Remove lines from the end until we have no more than 'count' from the beginning
pub fn resize_front(&self, count: usize) {
self.with_content(|c| {
while c.len() > count {
c.remove(c.len() - 1);
}
}) })
} }
@ -113,7 +145,7 @@ impl TextContent {
/// Apply the given closure to the inner content, and bust the cache afterward. /// Apply the given closure to the inner content, and bust the cache afterward.
pub fn with_content<F, O>(&self, f: F) -> O pub fn with_content<F, O>(&self, f: F) -> O
where where
F: FnOnce(&mut StyledString) -> O, F: FnOnce(&mut ContentType) -> O,
{ {
self.with_content_inner(|c| f(Arc::make_mut(&mut c.content_value))) self.with_content_inner(|c| f(Arc::make_mut(&mut c.content_value)))
} }
@ -141,7 +173,7 @@ impl TextContent {
struct TextContentInner { struct TextContentInner {
// content: String, // content: String,
content_value: InnerContentType, content_value: InnerContentType,
content_cache: InnerContentType, content_cache: InnerCacheType,
// We keep the cache here so it can be busted when we change the content. // We keep the cache here so it can be busted when we change the content.
size_cache: Option<XY<SizeCache>>, size_cache: Option<XY<SizeCache>>,
@ -167,7 +199,7 @@ impl TextContentInner {
} }
} }
fn get_cache(&self) -> &InnerContentType { fn get_cache(&self) -> &InnerCacheType {
&self.content_cache &self.content_cache
} }
} }
@ -194,6 +226,9 @@ pub struct CachedTextView {
// True if we can wrap long lines. // True if we can wrap long lines.
wrap: bool, wrap: bool,
// Maximum number of lines to keep while appending
max_lines: Option<usize>,
// ScrollBase make many scrolling-related things easier // ScrollBase make many scrolling-related things easier
width: Option<usize>, width: Option<usize>,
} }
@ -201,11 +236,11 @@ pub struct CachedTextView {
#[allow(dead_code)] #[allow(dead_code)]
impl CachedTextView { impl CachedTextView {
/// Creates a new TextView with the given content. /// Creates a new TextView with the given content.
pub fn new<S>(content: S, cache_size: usize) -> Self pub fn new<S>(content: S, cache_size: usize, max_lines: Option<usize>) -> Self
where where
S: Into<StyledString>, S: Into<ContentType>,
{ {
Self::new_with_content(TextContent::new(content), cache_size) Self::new_with_content(TextContent::new(content), cache_size, max_lines)
} }
/// Creates a new TextView using the given `TextContent`. /// Creates a new TextView using the given `TextContent`.
@ -224,7 +259,11 @@ impl CachedTextView {
/// content.set_content("new content"); /// content.set_content("new content");
/// assert!(view.get_content().source().contains("new")); /// assert!(view.get_content().source().contains("new"));
/// ``` /// ```
pub fn new_with_content(content: TextContent, cache_size: usize) -> Self { pub fn new_with_content(
content: TextContent,
cache_size: usize,
max_lines: Option<usize>,
) -> Self {
CachedTextView { CachedTextView {
cache: TinyCache::new(cache_size), cache: TinyCache::new(cache_size),
content, content,
@ -232,12 +271,13 @@ impl CachedTextView {
wrap: true, wrap: true,
align: Align::top_left(), align: Align::top_left(),
width: None, width: None,
max_lines,
} }
} }
/// Creates a new empty `TextView`. /// Creates a new empty `TextView`.
pub fn empty() -> Self { pub fn empty(cache_size: usize, max_lines: Option<usize>) -> Self {
CachedTextView::new("", 5) CachedTextView::new(ContentType::default(), cache_size, max_lines)
} }
/// Sets the style for the content. /// Sets the style for the content.
@ -307,7 +347,7 @@ impl CachedTextView {
#[must_use] #[must_use]
pub fn content<S>(self, content: S) -> Self pub fn content<S>(self, content: S) -> Self
where where
S: Into<StyledString>, S: Into<ContentType>,
{ {
self.with(|s| s.set_content(content)) self.with(|s| s.set_content(content))
} }
@ -315,19 +355,35 @@ impl CachedTextView {
/// Replace the text in this view. /// Replace the text in this view.
pub fn set_content<S>(&mut self, content: S) pub fn set_content<S>(&mut self, content: S)
where where
S: Into<StyledString>, S: Into<ContentType>,
{ {
self.cache.clear(); self.cache.clear();
self.content.set_content(content); self.content.set_content(content);
} }
/// Append `content` to the end of a `TextView`. /// Append `content` to the end of a `TextView`.
pub fn append<S>(&mut self, content: S) pub fn append_line<S>(&mut self, content: S)
where where
S: Into<StyledString>, S: Into<StyledString>,
{ {
self.cache.clear(); self.cache.clear();
self.content.append(content); self.content.append_line(content);
if let Some(max_lines) = self.max_lines {
self.content.resize_back(max_lines);
}
}
/// Append `content` lines to the end of a `TextView`.
pub fn append_lines<S, I>(&mut self, content: I)
where
I: Iterator<Item = S>,
S: Into<StyledString>,
{
self.cache.clear();
self.content.append_lines(content);
if let Some(max_lines) = self.max_lines {
self.content.resize_back(max_lines);
}
} }
/// Returns the current text in this view. /// Returns the current text in this view.
@ -357,7 +413,13 @@ impl CachedTextView {
// Completely bust the cache // Completely bust the cache
// Just in case we fail, we don't want to leave a bad cache. // Just in case we fail, we don't want to leave a bad cache.
content.size_cache = None; content.size_cache = None;
content.content_cache = Arc::clone(&content.content_value); content.content_cache = Arc::new(StyledString::from_iter(
content.content_value.iter().map(|s| {
let mut s = s.clone();
s.append_plain("\n");
s
}),
));
let rows = self.cache.compute(size.x, || { let rows = self.cache.compute(size.x, || {
LinesIterator::new(content.get_cache().as_ref(), size.x).collect() LinesIterator::new(content.get_cache().as_ref(), size.x).collect()
@ -522,13 +584,14 @@ mod tests {
#[test] #[test]
fn sanity() { fn sanity() {
let text_view = CachedTextView::new("", 5); let text_view = CachedTextView::new(ContentType::default(), 5, None);
assert_eq!(text_view.get_content().data.spans().len(), 1); assert_eq!(text_view.get_content().data.len(), 0);
} }
#[test] #[test]
fn test_cache() { fn test_cache() {
let mut text_view = CachedTextView::new("sample", 3); let mut text_view =
CachedTextView::new(VecDeque::from([StyledString::from("sample")]), 3, None);
assert!(text_view.cache.is_empty()); assert!(text_view.cache.is_empty());
text_view.compute_rows(Vec2::new(0, 0)); text_view.compute_rows(Vec2::new(0, 0));
@ -547,11 +610,11 @@ mod tests {
assert_eq!(text_view.cache.keys(), [(&0, 1), (&2, 0), (&3, 0)]); assert_eq!(text_view.cache.keys(), [(&0, 1), (&2, 0), (&3, 0)]);
text_view.set_content(""); text_view.set_content(VecDeque::new());
assert_eq!(text_view.cache.len(), 0); assert_eq!(text_view.cache.len(), 0);
text_view.compute_rows(Vec2::new(0, 0)); text_view.compute_rows(Vec2::new(0, 0));
text_view.append("sample"); text_view.append_line("sample");
assert_eq!(text_view.cache.len(), 0); assert_eq!(text_view.cache.len(), 0);
text_view.compute_rows(Vec2::new(0, 0)); text_view.compute_rows(Vec2::new(0, 0));

View File

@ -20,7 +20,7 @@ logging:
append: true append: true
interface: interface:
node_log: node_log:
scrollback: 2048 scrollback: 10000
command_line: command_line:
history_size: 2048 history_size: 2048
theme: theme:

View File

@ -223,7 +223,7 @@ impl UI {
}); });
} }
fn clear_handler(siv: &mut Cursive) { fn clear_handler(siv: &mut Cursive) {
Self::node_events_view(siv).set_content(""); Self::node_events_view(siv).set_content([]);
UI::update_cb(siv); UI::update_cb(siv);
} }
@ -262,15 +262,22 @@ impl UI {
} }
//////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////
pub fn push_styled(s: &mut Cursive, styled_string: StyledString) { fn push_styled_line(s: &mut Cursive, styled_string: StyledString) {
let mut ctv = UI::node_events_view(s); let mut ctv = UI::node_events_view(s);
ctv.append(styled_string) ctv.append_line(styled_string)
} }
pub fn push_ansi_lines(s: &mut Cursive, starting_style: Style, lines: String) { fn push_ansi_lines(s: &mut Cursive, mut starting_style: Style, lines: String) {
let mut ctv = UI::node_events_view(s); let mut ctv = UI::node_events_view(s);
let (spanned_string, _end_style) = ansi::parse_with_starting_style(starting_style, lines);
ctv.append(spanned_string); let mut sslines: Vec<StyledString> = vec![];
for line in lines.lines() {
let (spanned_string, end_style) = ansi::parse_with_starting_style(starting_style, line);
sslines.push(spanned_string);
starting_style = end_style;
}
ctv.append_lines(sslines.into_iter());
} }
//////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////
@ -883,7 +890,7 @@ impl UI {
node_log_scrollback: usize, node_log_scrollback: usize,
) -> ResizedView<NamedView<NodeEventsPanel>> { ) -> ResizedView<NamedView<NodeEventsPanel>> {
Panel::new( Panel::new(
CachedTextView::new("", node_log_scrollback) CachedTextView::new([], node_log_scrollback, Some(node_log_scrollback))
.with_name("node-events-view") .with_name("node-events-view")
.scrollable() .scrollable()
.scroll_strategy(cursive::view::ScrollStrategy::StickToBottom) .scroll_strategy(cursive::view::ScrollStrategy::StickToBottom)
@ -1167,7 +1174,7 @@ impl UISender {
pub fn push_styled(&self, styled_string: StyledString) -> std::io::Result<()> { pub fn push_styled(&self, styled_string: StyledString) -> std::io::Result<()> {
let res = self.cb_sink.send(Box::new(move |s| { let res = self.cb_sink.send(Box::new(move |s| {
UI::push_styled(s, styled_string); UI::push_styled_line(s, styled_string);
})); }));
if res.is_err() { if res.is_err() {
return Err(std::io::Error::from(std::io::ErrorKind::BrokenPipe)); return Err(std::io::Error::from(std::io::ErrorKind::BrokenPipe));
@ -1206,6 +1213,7 @@ impl LogWriter for UISender {
for argline in args.lines() { for argline in args.lines() {
line.append_styled(argline, color); line.append_styled(argline, color);
line.append_plain("\n");
self.push_styled(line)?; self.push_styled(line)?;
line = StyledString::new(); line = StyledString::new();
line.append_plain(" ".repeat(indent)); line.append_plain(" ".repeat(indent));