From 40e4bfbe15bd06703b92c539f711564b6046db4e Mon Sep 17 00:00:00 2001 From: rhysd Date: Sun, 17 Jul 2022 03:22:09 +0900 Subject: [PATCH 1/2] render only displayed lines in `Paragraph` by handling vertical scroll by ourselves --- src/widget.rs | 66 +++++++++++++++++++++++---------------------------- 1 file changed, 30 insertions(+), 36 deletions(-) diff --git a/src/widget.rs b/src/widget.rs index 89cf1e6..dd2bf0f 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -4,7 +4,7 @@ use std::sync::atomic::{AtomicU32, Ordering}; use tui::buffer::Buffer; use tui::layout::Rect; use tui::text::Text; -use tui::widgets::{Block, Paragraph, Widget}; +use tui::widgets::{Paragraph, Widget}; // &mut 'a (u16, u16) is not available since TextAreaWidget instance totally takes over the ownership of TextArea // instance. In the case, the TextArea instance cannot be accessed from any other objects since it is mutablly @@ -36,40 +36,18 @@ impl ScrollTop { } } -pub struct Renderer<'a> { - scroll_top: &'a ScrollTop, - cursor: (u16, u16), - block: Option>, - inner: Paragraph<'a>, -} +pub struct Renderer<'a>(&'a TextArea<'a>); impl<'a> Renderer<'a> { pub fn new(textarea: &'a TextArea<'a>) -> Self { - let lnum_len = num_digits(textarea.lines().len()); - let lines: Vec<_> = textarea - .lines() - .iter() - .map(String::as_str) - .enumerate() - .map(|(row, line)| textarea.line_spans(line, row, lnum_len)) - .collect(); - let inner = Paragraph::new(Text::from(lines)).style(textarea.style()); - let cursor = textarea.cursor(); - Self { - scroll_top: &textarea.scroll_top, - cursor: (cursor.0 as u16, cursor.1 as u16), - block: textarea.block().cloned(), - inner, - } + Self(textarea) } } impl<'a> Widget for Renderer<'a> { - fn render(mut self, area: Rect, buf: &mut Buffer) { - let inner_area = if let Some(b) = self.block.take() { - let area = b.inner(area); - self.inner = self.inner.block(b); - area + fn render(self, area: Rect, buf: &mut Buffer) { + let inner_area = if let Some(b) = self.0.block() { + b.inner(area) } else { area }; @@ -84,18 +62,34 @@ impl<'a> Widget for Renderer<'a> { } } - let (top_row, top_col) = self.scroll_top.load(); - let row = next_scroll_top(top_row, self.cursor.0, inner_area.height); - let col = next_scroll_top(top_col, self.cursor.1, inner_area.width); + let cursor = self.0.cursor(); + let (top_row, top_col) = self.0.scroll_top.load(); + let row = next_scroll_top(top_row, cursor.0 as u16, inner_area.height); + let col = next_scroll_top(top_col, cursor.1 as u16, inner_area.width); - let scroll = (row, col); - if scroll != (0, 0) { - self.inner = self.inner.scroll(scroll); + let lnum_len = num_digits(self.0.lines().len()); + let lines: Vec<_> = self + .0 + .lines() + .iter() + .map(String::as_str) + .enumerate() + .skip(row as usize) + .take(inner_area.height as usize) + .map(|(row, line)| self.0.line_spans(line, row, lnum_len)) + .collect(); + + let mut inner = Paragraph::new(Text::from(lines)).style(self.0.style()); + if let Some(b) = self.0.block() { + inner = inner.block(b.clone()); + } + if col != 0 { + inner = inner.scroll((0, col)); } // Store scroll top position for rendering on the next tick - self.scroll_top.store(row, col); + self.0.scroll_top.store(row, col); - self.inner.render(area, buf); + inner.render(area, buf); } } From ee4162c4519a17b0fffa13841885db807470d288 Mon Sep 17 00:00:00 2001 From: rhysd Date: Sun, 17 Jul 2022 12:09:08 +0900 Subject: [PATCH 2/2] explicitly use subslice of lines this improved performance by 2~5% ``` insert::append::1_lorem time: [7.0412 ms 7.0825 ms 7.1268 ms] change: [-8.5796% -7.8935% -7.1723%] (p = 0.00 < 0.05) Performance has improved. Found 2 outliers among 100 measurements (2.00%) 1 (1.00%) high mild 1 (1.00%) high severe Benchmarking insert::append::10_lorem: Warming up for 3.0000 s Warning: Unable to complete 100 samples in 5.0s. You may wish to increase target time to 33.6s, or reduce sample count to 10. insert::append::10_lorem time: [328.15 ms 329.60 ms 331.12 ms] change: [-9.6975% -9.1606% -8.6231%] (p = 0.00 < 0.05) Performance has improved. Found 1 outliers among 100 measurements (1.00%) 1 (1.00%) high mild Benchmarking insert::append::50_lorem: Warming up for 3.0000 s Warning: Unable to complete 100 samples in 5.0s. You may wish to increase target time to 321.1s, or reduce sample count to 10. insert::append::50_lorem time: [3.1633 s 3.1727 s 3.1826 s] change: [-7.3473% -7.0339% -6.6629%] (p = 0.00 < 0.05) Performance has improved. ``` --- src/widget.rs | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/widget.rs b/src/widget.rs index dd2bf0f..4e21cf6 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -1,5 +1,6 @@ use crate::textarea::TextArea; use crate::util::num_digits; +use std::cmp; use std::sync::atomic::{AtomicU32, Ordering}; use tui::buffer::Buffer; use tui::layout::Rect; @@ -42,6 +43,18 @@ impl<'a> Renderer<'a> { pub fn new(textarea: &'a TextArea<'a>) -> Self { Self(textarea) } + + #[inline] + fn text(&self, top_row: usize, height: usize) -> Text<'a> { + let lines_len = self.0.lines().len(); + let lnum_len = num_digits(lines_len); + let bottom_row = cmp::min(top_row + height, lines_len); + let mut lines = Vec::with_capacity(bottom_row - top_row); + for (i, line) in self.0.lines()[top_row..bottom_row].iter().enumerate() { + lines.push(self.0.line_spans(line.as_str(), top_row + i, lnum_len)); + } + Text::from(lines) + } } impl<'a> Widget for Renderer<'a> { @@ -64,31 +77,20 @@ impl<'a> Widget for Renderer<'a> { let cursor = self.0.cursor(); let (top_row, top_col) = self.0.scroll_top.load(); - let row = next_scroll_top(top_row, cursor.0 as u16, inner_area.height); - let col = next_scroll_top(top_col, cursor.1 as u16, inner_area.width); + let top_row = next_scroll_top(top_row, cursor.0 as u16, inner_area.height); + let top_col = next_scroll_top(top_col, cursor.1 as u16, inner_area.width); - let lnum_len = num_digits(self.0.lines().len()); - let lines: Vec<_> = self - .0 - .lines() - .iter() - .map(String::as_str) - .enumerate() - .skip(row as usize) - .take(inner_area.height as usize) - .map(|(row, line)| self.0.line_spans(line, row, lnum_len)) - .collect(); - - let mut inner = Paragraph::new(Text::from(lines)).style(self.0.style()); + let text = self.text(top_row as usize, inner_area.height as usize); + let mut inner = Paragraph::new(text).style(self.0.style()); if let Some(b) = self.0.block() { inner = inner.block(b.clone()); } - if col != 0 { - inner = inner.scroll((0, col)); + if top_col != 0 { + inner = inner.scroll((0, top_col)); } // Store scroll top position for rendering on the next tick - self.0.scroll_top.store(row, col); + self.0.scroll_top.store(top_row, top_col); inner.render(area, buf); }