summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorManos Pitsidianakis <el13635@mail.ntua.gr>2020-12-01 00:49:56 +0200
committerManos Pitsidianakis <el13635@mail.ntua.gr>2020-12-01 01:04:27 +0200
commit857d4d546f0ef9b0a766305560bdc8d1a30f746d (patch)
tree3e434c97a27e906eaafd3fdac391d4fcc46601b2
parent5327dae02d65a9ce0bc27b986693952dbc87f4a6 (diff)
utilities/pager: use LineBreakText for lazy line breaking
-rw-r--r--src/components/utilities/pager.rs347
1 files changed, 186 insertions, 161 deletions
diff --git a/src/components/utilities/pager.rs b/src/components/utilities/pager.rs
index 0561180b..e80b2a58 100644
--- a/src/components/utilities/pager.rs
+++ b/src/components/utilities/pager.rs
@@ -20,6 +20,7 @@
*/
use super::*;
+use melib::text_processing::LineBreakText;
/// A pager for text.
/// `Pager` holds its own content in its own `CellBuffer` and when `draw` is called, it draws the
@@ -39,7 +40,8 @@ pub struct Pager {
initialised: bool,
show_scrollbar: bool,
content: CellBuffer,
- text_lines: (usize, Vec<String>),
+ text_lines: Vec<String>,
+ line_breaker: LineBreakText,
movement: Option<PageMovement>,
id: ComponentId,
}
@@ -95,23 +97,17 @@ impl Pager {
}
}
- let lines: Vec<String> = text.split_lines_reflow(self.reflow, width);
- let height = lines.len() + 2;
- let width = width.unwrap_or_else(|| lines.iter().map(|l| l.len()).max().unwrap_or(0));
- let mut empty_cell = Cell::with_char(' ');
- empty_cell.set_fg(self.colors.fg);
- empty_cell.set_bg(self.colors.bg);
- let mut content = CellBuffer::new(1, 1, empty_cell);
- content.set_ascii_drawing(self.content.ascii_drawing);
self.text = text.to_string();
- self.text_lines = (0, vec![]);
- self.content = content;
- self.height = height;
- self.width = width;
+ self.text_lines.clear();
+ self.line_breaker = LineBreakText::new(self.text.clone(), self.reflow, width);
+ self.height = 0;
+ self.width = 0;
+ self.search = None;
+ self.set_dirty(true);
self.initialised = false;
- self.dirty = true;
self.cursor = (0, 0);
}
+
pub fn from_string(
mut text: String,
context: Option<&Context>,
@@ -178,56 +174,21 @@ impl Pager {
}) {
return Pager::from_buf(content, cursor_pos);
}
- let content = {
- if let Some(context) = context {
- CellBuffer::new_with_context(1, 1, None, context)
- } else {
- let mut empty_cell = Cell::with_char(' ');
- empty_cell.set_fg(colors.fg);
- empty_cell.set_bg(colors.bg);
- CellBuffer::new(1, 1, empty_cell)
- }
- };
Pager {
text,
- text_lines: (0, vec![]),
+ text_lines: vec![],
reflow,
cursor: (0, cursor_pos.unwrap_or(0)),
- height: content.size().1,
- width: content.size().0,
+ height: 1,
+ width: 1,
minimum_width: pager_minimum_width,
initialised: false,
dirty: true,
- content,
id: ComponentId::new_v4(),
colors,
..Default::default()
}
}
- pub fn from_str(
- text: &str,
- cursor_pos: Option<usize>,
- width: Option<usize>,
- colors: ThemeAttribute,
- ) -> Self {
- let mut empty_cell = Cell::with_char(' ');
- empty_cell.set_fg(colors.fg);
- empty_cell.set_bg(colors.bg);
-
- Pager {
- text: text.to_string(),
- text_lines: (0, vec![]),
- cursor: (0, cursor_pos.unwrap_or(0)),
- height: 1,
- width: width.unwrap_or(1),
- initialised: false,
- dirty: true,
- content: CellBuffer::new(1, 1, empty_cell),
- colors,
- id: ComponentId::new_v4(),
- ..Default::default()
- }
- }
pub fn from_buf(content: CellBuffer, cursor_pos: Option<usize>) -> Self {
let (width, height) = content.size();
@@ -243,25 +204,6 @@ impl Pager {
..Default::default()
}
}
- pub fn print_string(content: &mut CellBuffer, lines: &[String], colors: ThemeAttribute) {
- let width = content.size().0;
- debug!(colors);
- for (i, l) in lines.iter().enumerate() {
- write_string_to_grid(
- l,
- content,
- colors.fg,
- colors.bg,
- Attr::DEFAULT,
- ((0, i), (width.saturating_sub(1), i)),
- None,
- );
- if l.starts_with("⤷") {
- content[(0, i)].set_fg(Color::Byte(240));
- content[(0, i)].set_attrs(Attr::BOLD);
- }
- }
- }
pub fn cursor_pos(&self) -> usize {
self.cursor.1
@@ -270,89 +212,174 @@ impl Pager {
pub fn size(&self) -> (usize, usize) {
(self.width, self.height)
}
-}
-impl Component for Pager {
- fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
- if !is_valid_area!(area) {
- return;
+ pub fn initialise(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
+ let mut width = width!(area);
+ if width < self.minimum_width {
+ width = self.minimum_width;
}
- if !self.is_dirty() {
- return;
+ if self.line_breaker.width() != Some(width.saturating_sub(4)) {
+ let line_breaker = LineBreakText::new(
+ self.text.clone(),
+ self.reflow,
+ Some(width.saturating_sub(4)),
+ );
+
+ self.line_breaker = line_breaker;
+ self.text_lines.clear();
+ };
+ self.height = self.text_lines.len();
+ self.width = width;
+ if let Some(ref mut search) = self.search {
+ use melib::text_processing::search::KMP;
+ search.positions.clear();
+ for (y, l) in self.text_lines.iter().enumerate() {
+ search.positions.extend(
+ l.kmp_search(&search.pattern)
+ .into_iter()
+ .map(|offset| (y, offset)),
+ );
+ }
}
+ self.draw_lines_up_to(grid, area, context, self.cursor.1 + 2 * height!(area));
+ self.draw_page(grid, area, context);
+ self.initialised = true;
+ }
- if !self.initialised && !self.text.is_empty() {
- let mut width = width!(area);
- if width < self.minimum_width {
- width = self.minimum_width;
+ pub fn draw_lines_up_to(
+ &mut self,
+ _grid: &mut CellBuffer,
+ area: Area,
+ _context: &mut Context,
+ up_to: usize,
+ ) {
+ if self.line_breaker.is_finished() {
+ return;
+ }
+ let old_lines_no = self.text_lines.len();
+ if up_to == 0 {
+ self.text_lines.extend(self.line_breaker.by_ref());
+ } else {
+ if old_lines_no >= up_to + height!(area) {
+ return;
+ }
+ let new_lines_no = (up_to + height!(area)) - old_lines_no;
+ self.text_lines
+ .extend(self.line_breaker.by_ref().take(new_lines_no));
+ };
+ let new_lines_no = self.text_lines.len() - old_lines_no;
+ if let Some(ref mut search) = self.search {
+ use melib::text_processing::search::KMP;
+ for (y, l) in self.text_lines.iter().enumerate().skip(old_lines_no) {
+ search.positions.extend(
+ l.kmp_search(&search.pattern)
+ .into_iter()
+ .map(|offset| (y, offset)),
+ );
}
+ }
+ self.height += new_lines_no;
+ }
- let lines: &[String] = if self.text_lines.0 == width.saturating_sub(4) {
- &self.text_lines.1
- } else {
- let lines = self
- .text
- .split_lines_reflow(self.reflow, Some(width.saturating_sub(4)));
- self.text_lines = (width.saturating_sub(4), lines);
- &self.text_lines.1
- };
- let height = lines.len() + 2;
- let mut empty_cell = Cell::with_char(' ');
- empty_cell.set_fg(self.colors.fg);
- empty_cell.set_bg(self.colors.bg);
- let mut content = CellBuffer::new(width, height, empty_cell);
- content.set_ascii_drawing(self.content.ascii_drawing);
- if let Some(ref mut search) = self.search {
- use melib::text_processing::search::KMP;
- search.positions.clear();
- for (y, l) in lines.iter().enumerate() {
- search.positions.extend(
- l.kmp_search(&search.pattern)
- .into_iter()
- .map(|offset| (y, offset)),
- );
- }
+ fn draw_page(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
+ let (mut upper_left, bottom_right) = area;
+ for l in self
+ .text_lines
+ .iter()
+ .skip(self.cursor.1)
+ .take(height!(area) + 1)
+ {
+ write_string_to_grid(
+ l,
+ grid,
+ self.colors.fg,
+ self.colors.bg,
+ Attr::DEFAULT,
+ (upper_left, bottom_right),
+ None,
+ );
+ if l.starts_with("⤷") {
+ grid[upper_left]
+ .set_fg(Color::Byte(240))
+ .set_attrs(Attr::BOLD);
}
- Pager::print_string(&mut content, &lines, self.colors);
- #[cfg(feature = "regexp")]
- {
- for text_formatter in
- crate::conf::text_format_regexps(context, "pager.envelope.body")
+ upper_left = pos_inc(upper_left, (0, 1));
+ }
+
+ if get_y(upper_left) <= get_y(bottom_right) {
+ clear_area(
+ grid,
+ (upper_left, bottom_right),
+ crate::conf::value(context, "theme_default"),
+ );
+ }
+
+ let (upper_left, _bottom_right) = area;
+ #[cfg(feature = "regexp")]
+ {
+ for text_formatter in crate::conf::text_format_regexps(context, "pager.envelope.body") {
+ let t = grid.insert_tag(text_formatter.tag);
+ for (i, l) in self
+ .text_lines
+ .iter()
+ .skip(self.cursor.1)
+ .enumerate()
+ .take(height!(area) + 1)
{
- let t = content.insert_tag(text_formatter.tag);
- for (i, l) in lines.iter().enumerate() {
- for (start, end) in text_formatter.regexp.find_iter(l) {
- content.set_tag(t, (start, i), (end, i));
- }
+ let i = i + get_y(upper_left);
+ for (start, end) in text_formatter.regexp.find_iter(l) {
+ let start = start + get_x(upper_left);
+ let end = end + get_x(upper_left);
+ grid.set_tag(t, (start, i), (end, i));
}
}
}
- if let Some(ref mut search) = self.search {
- let results_attr = crate::conf::value(context, "pager.highlight_search");
- let results_current_attr =
- crate::conf::value(context, "pager.highlight_search_current");
- search.cursor =
- std::cmp::min(search.positions.len().saturating_sub(1), search.cursor);
- for (i, (y, x)) in search.positions.iter().enumerate() {
- for c in content.row_iter(*x..*x + search.pattern.grapheme_len(), *y) {
- if i == search.cursor {
- content[c]
- .set_fg(results_current_attr.fg)
- .set_bg(results_current_attr.bg)
- .set_attrs(results_current_attr.attrs);
- } else {
- content[c]
- .set_fg(results_attr.fg)
- .set_bg(results_attr.bg)
- .set_attrs(results_attr.attrs);
- }
+ }
+ let cursor_line = self.cursor.1;
+ if let Some(ref mut search) = self.search {
+ let results_attr = crate::conf::value(context, "pager.highlight_search");
+ let results_current_attr =
+ crate::conf::value(context, "pager.highlight_search_current");
+ search.cursor = std::cmp::min(search.positions.len().saturating_sub(1), search.cursor);
+ for (i, (y, x)) in search
+ .positions
+ .iter()
+ .enumerate()
+ .filter(|(_, (y, _))| *y >= cursor_line)
+ .take(height!(area) + 1)
+ {
+ let x = *x + get_x(upper_left);
+ let y = *y - cursor_line;
+ for c in grid.row_iter(x..x + search.pattern.grapheme_len(), y + get_y(upper_left))
+ {
+ if i == search.cursor {
+ grid[c]
+ .set_fg(results_current_attr.fg)
+ .set_bg(results_current_attr.bg)
+ .set_attrs(results_current_attr.attrs);
+ } else {
+ grid[c]
+ .set_fg(results_attr.fg)
+ .set_bg(results_attr.bg)
+ .set_attrs(results_attr.attrs);
}
}
}
- self.content = content;
- self.height = height;
- self.width = width;
- self.initialised = true;
+ }
+ }
+}
+
+impl Component for Pager {
+ fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
+ if !is_valid_area!(area) {
+ return;
+ }
+ if !self.is_dirty() {
+ return;
+ }
+
+ if !self.initialised {
+ self.initialise(grid, area, context);
}
self.dirty = false;
@@ -372,15 +399,17 @@ impl Component for Pager {
} else {
self.cursor.1 = self.height.saturating_sub(1);
}
+ self.draw_lines_up_to(grid, area, context, self.cursor.1 + height);
}
PageMovement::PageDown(multiplier) => {
if self.cursor.1 + height * multiplier + 1 < self.height {
self.cursor.1 += height * multiplier;
} else if self.cursor.1 + height * multiplier > self.height {
- self.cursor.1 = self.height - 1;
+ self.cursor.1 = self.height.saturating_sub(1);
} else {
self.cursor.1 = (self.height / height) * height;
}
+ self.draw_lines_up_to(grid, area, context, self.cursor.1 + height);
}
PageMovement::Right(amount) => {
if self.cursor.0 + amount + 1 < self.width {
@@ -397,6 +426,7 @@ impl Component for Pager {
}
PageMovement::End => {
self.cursor.1 = self.height.saturating_sub(1);
+ self.draw_lines_up_to(grid, area, context, 0);
}
}
}
@@ -425,8 +455,11 @@ impl Component for Pager {
}
clear_area(grid, area, crate::conf::value(context, "theme_default"));
- let (width, height) = self.content.size();
let (mut cols, mut rows) = (width!(area), height!(area));
+ if cols < 2 || rows < 2 {
+ return;
+ }
+ let (width, height) = (self.line_breaker.width().unwrap_or(cols), self.height);
if self.show_scrollbar && rows < height {
cols -= 1;
}
@@ -437,22 +470,8 @@ impl Component for Pager {
std::cmp::min(width.saturating_sub(cols), self.cursor.0),
std::cmp::min(height.saturating_sub(rows), self.cursor.1),
);
- copy_area(
- grid,
- &self.content,
- area,
- (
- (
- std::cmp::min((width - 1).saturating_sub(cols), self.cursor.0),
- std::cmp::min((height - 1).saturating_sub(rows), self.cursor.1),
- ),
- (
- std::cmp::min(self.cursor.0 + cols, width - 1),
- std::cmp::min(self.cursor.1 + rows, height - 1),
- ),
- ),
- );
- if self.show_scrollbar && rows + 1 < height {
+ self.draw_page(grid, area, context);
+ if self.show_scrollbar && rows < height {
ScrollBar::default().set_show_arrows(true).draw(
grid,
(
@@ -460,12 +479,15 @@ impl Component for Pager {
bottom_right!(area),
),
context,
+ /* position */
self.cursor.1,
+ /* visible_rows */
rows,
+ /* length */
height,
);
}
- if self.show_scrollbar && cols + 1 < width {
+ if self.show_scrollbar && cols < width {
ScrollBar::default().set_show_arrows(true).draw_horizontal(
grid,
(
@@ -480,20 +502,21 @@ impl Component for Pager {
}
context.dirty_areas.push_back(area);
}
+
fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
let shortcuts = self.get_shortcuts(context);
match event {
UIEvent::Input(ref key)
if shortcut!(key == shortcuts[Self::DESCRIPTION]["scroll_up"]) =>
{
- self.cursor.1 = self.cursor.1.saturating_sub(1);
+ self.movement = Some(PageMovement::Up(1));
self.dirty = true;
return true;
}
UIEvent::Input(ref key)
if shortcut!(key == shortcuts[Self::DESCRIPTION]["scroll_down"]) =>
{
- self.cursor.1 = self.cursor.1 + 1;
+ self.movement = Some(PageMovement::Down(1));
self.dirty = true;
return true;
}
@@ -620,6 +643,7 @@ impl Component for Pager {
}
false
}
+
fn is_dirty(&self) -> bool {
self.dirty
}
@@ -627,6 +651,7 @@ impl Component for Pager {
fn set_dirty(&mut self, value: bool) {
self.dirty = value;
}
+
fn get_shortcuts(&self, context: &Context) -> ShortcutMaps {
let config_map: IndexMap<&'static str, Key> = context.settings.shortcuts.pager.key_values();
let mut ret: ShortcutMaps = Default::default();