mirror of
https://gitlab.com/veilid/veilid.git
synced 2024-10-01 01:26:08 -04:00
cap scrollback
This commit is contained in:
parent
4acb760c10
commit
b791a82ec6
@ -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));
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
@ -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));
|
||||||
|
Loading…
Reference in New Issue
Block a user