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::sync::Arc;
use std::sync::{Mutex, MutexGuard};
@ -12,7 +13,32 @@ use owning_ref::{ArcRef, OwningHandle};
use unicode_width::UnicodeWidthStr;
// 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`].
///
@ -36,69 +62,75 @@ pub struct TextContent {
content: Arc<Mutex<TextContentInner>>,
}
#[allow(dead_code)]
impl TextContent {
/// Creates a new text content around the given value.
///
/// Parses the given value.
pub fn new<S>(content: S) -> Self
where
S: Into<StyledString>,
S: Into<ContentType>,
{
let content = Arc::new(content.into());
TextContent {
content: Arc::new(Mutex::new(TextContentInner {
content_value: content,
content_cache: Arc::new(StyledString::default()),
content_cache: Arc::new(CacheType::default()),
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.
pub fn set_content<S>(&self, content: S)
where
S: Into<StyledString>,
S: Into<ContentType>,
{
self.with_content(|c| {
*c = content.into();
});
}
/// Append `content` to the end of a `TextView`.
pub fn append<S>(&self, content: S)
/// Append `line` to the end of a `TextView`.
pub fn append_line<S>(&self, line: S)
where
S: Into<StyledString>,
{
self.with_content(|c| {
// This will only clone content if content_cached and content_value
// are sharing the same underlying Rc.
c.append(content);
c.push_back(line.into());
})
}
/// 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.
pub fn with_content<F, O>(&self, f: F) -> O
where
F: FnOnce(&mut StyledString) -> O,
F: FnOnce(&mut ContentType) -> O,
{
self.with_content_inner(|c| f(Arc::make_mut(&mut c.content_value)))
}
@ -141,7 +173,7 @@ impl TextContent {
struct TextContentInner {
// content: String,
content_value: InnerContentType,
content_cache: InnerContentType,
content_cache: InnerCacheType,
// We keep the cache here so it can be busted when we change the content.
size_cache: Option<XY<SizeCache>>,
@ -167,7 +199,7 @@ impl TextContentInner {
}
}
fn get_cache(&self) -> &InnerContentType {
fn get_cache(&self) -> &InnerCacheType {
&self.content_cache
}
}
@ -194,6 +226,9 @@ pub struct CachedTextView {
// True if we can wrap long lines.
wrap: bool,
// Maximum number of lines to keep while appending
max_lines: Option<usize>,
// ScrollBase make many scrolling-related things easier
width: Option<usize>,
}
@ -201,11 +236,11 @@ pub struct CachedTextView {
#[allow(dead_code)]
impl CachedTextView {
/// 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
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`.
@ -224,7 +259,11 @@ impl CachedTextView {
/// content.set_content("new content");
/// 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 {
cache: TinyCache::new(cache_size),
content,
@ -232,12 +271,13 @@ impl CachedTextView {
wrap: true,
align: Align::top_left(),
width: None,
max_lines,
}
}
/// Creates a new empty `TextView`.
pub fn empty() -> Self {
CachedTextView::new("", 5)
pub fn empty(cache_size: usize, max_lines: Option<usize>) -> Self {
CachedTextView::new(ContentType::default(), cache_size, max_lines)
}
/// Sets the style for the content.
@ -307,7 +347,7 @@ impl CachedTextView {
#[must_use]
pub fn content<S>(self, content: S) -> Self
where
S: Into<StyledString>,
S: Into<ContentType>,
{
self.with(|s| s.set_content(content))
}
@ -315,19 +355,35 @@ impl CachedTextView {
/// Replace the text in this view.
pub fn set_content<S>(&mut self, content: S)
where
S: Into<StyledString>,
S: Into<ContentType>,
{
self.cache.clear();
self.content.set_content(content);
}
/// 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
S: Into<StyledString>,
{
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.
@ -357,7 +413,13 @@ impl CachedTextView {
// Completely bust the cache
// Just in case we fail, we don't want to leave a bad cache.
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, || {
LinesIterator::new(content.get_cache().as_ref(), size.x).collect()
@ -522,13 +584,14 @@ mod tests {
#[test]
fn sanity() {
let text_view = CachedTextView::new("", 5);
assert_eq!(text_view.get_content().data.spans().len(), 1);
let text_view = CachedTextView::new(ContentType::default(), 5, None);
assert_eq!(text_view.get_content().data.len(), 0);
}
#[test]
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());
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)]);
text_view.set_content("");
text_view.set_content(VecDeque::new());
assert_eq!(text_view.cache.len(), 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);
text_view.compute_rows(Vec2::new(0, 0));

View File

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

View File

@ -223,7 +223,7 @@ impl UI {
});
}
fn clear_handler(siv: &mut Cursive) {
Self::node_events_view(siv).set_content("");
Self::node_events_view(siv).set_content([]);
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);
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 (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,
) -> ResizedView<NamedView<NodeEventsPanel>> {
Panel::new(
CachedTextView::new("", node_log_scrollback)
CachedTextView::new([], node_log_scrollback, Some(node_log_scrollback))
.with_name("node-events-view")
.scrollable()
.scroll_strategy(cursive::view::ScrollStrategy::StickToBottom)
@ -1167,7 +1174,7 @@ impl UISender {
pub fn push_styled(&self, styled_string: StyledString) -> std::io::Result<()> {
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() {
return Err(std::io::Error::from(std::io::ErrorKind::BrokenPipe));
@ -1206,6 +1213,7 @@ impl LogWriter for UISender {
for argline in args.lines() {
line.append_styled(argline, color);
line.append_plain("\n");
self.push_styled(line)?;
line = StyledString::new();
line.append_plain(" ".repeat(indent));