led/src/term_ui/formatter.rs
Nathan Vegdahl a1d636a4d8 WIP: handling extremely long lines with good performance.
The formatters now work on grapheme iterators instead of directly on
lines, which frees up the LineFormatter to break up long lines into
smaller blocks of text.  This is partially taken advantage of right
now in various parts of the code, but more work is still needed to
get it both working properly and fast.
2015-02-15 22:41:11 -08:00

165 lines
4.3 KiB
Rust

use std::cmp::max;
use string_utils::{is_line_ending};
use formatter::{LineFormatter, RoundingBehavior};
//===================================================================
// LineFormatter implementation for terminals/consoles.
//===================================================================
pub struct ConsoleLineFormatter {
pub tab_width: u8,
pub wrap_width: usize,
}
impl ConsoleLineFormatter {
pub fn new(tab_width: u8) -> ConsoleLineFormatter {
ConsoleLineFormatter {
tab_width: tab_width,
wrap_width: 40,
}
}
pub fn iter<'a, T>(&'a self, g_iter: T) -> ConsoleLineFormatterVisIter<'a, T>
where T: Iterator<Item=&'a str>
{
ConsoleLineFormatterVisIter::<'a, T> {
grapheme_iter: g_iter,
f: self,
pos: (0, 0),
}
}
}
impl LineFormatter for ConsoleLineFormatter {
fn single_line_height(&self) -> usize {
return 1;
}
fn dimensions<'a, T>(&'a self, g_iter: T) -> (usize, usize)
where T: Iterator<Item=&'a str>
{
let mut dim: (usize, usize) = (0, 0);
for (_, pos, width) in self.iter(g_iter) {
dim = (max(dim.0, pos.0), max(dim.1, pos.1 + width));
}
dim.0 += self.single_line_height();
return dim;
}
fn index_to_v2d<'a, T>(&'a self, g_iter: T, index: usize) -> (usize, usize)
where T: Iterator<Item=&'a str>
{
let mut pos = (0, 0);
let mut i = 0;
let mut last_width = 0;
for (_, _pos, width) in self.iter(g_iter) {
pos = _pos;
last_width = width;
i += 1;
if i > index {
return pos;
}
}
return (pos.0, pos.1 + last_width);
}
fn v2d_to_index<'a, T>(&'a self, g_iter: T, v2d: (usize, usize), _: (RoundingBehavior, RoundingBehavior)) -> usize
where T: Iterator<Item=&'a str>
{
// TODO: handle rounding modes
let mut i = 0;
for (_, pos, _) in self.iter(g_iter) {
if pos.0 > v2d.0 {
break;
}
else if pos.0 == v2d.0 && pos.1 >= v2d.1 {
break;
}
i += 1;
}
return i;
}
}
//===================================================================
// An iterator that iterates over the graphemes in a line in a
// manner consistent with the ConsoleFormatter.
//===================================================================
pub struct ConsoleLineFormatterVisIter<'a, T>
where T: Iterator<Item=&'a str>
{
grapheme_iter: T,
f: &'a ConsoleLineFormatter,
pos: (usize, usize),
}
impl<'a, T> Iterator for ConsoleLineFormatterVisIter<'a, T>
where T: Iterator<Item=&'a str>
{
type Item = (&'a str, (usize, usize), usize);
fn next(&mut self) -> Option<(&'a str, (usize, usize), usize)> {
if let Some(g) = self.grapheme_iter.next() {
let width = grapheme_vis_width_at_vis_pos(g, self.pos.1, self.f.tab_width as usize);
if (self.pos.1 + width) > self.f.wrap_width {
let pos = (self.pos.0 + self.f.single_line_height(), 0);
self.pos = (self.pos.0 + self.f.single_line_height(), width);
return Some((g, pos, width));
}
else {
let pos = self.pos;
self.pos = (self.pos.0, self.pos.1 + width);
return Some((g, pos, width));
}
}
else {
return None;
}
}
}
//===================================================================
// Helper functions
//===================================================================
/// Returns the visual width of a grapheme given a starting
/// position on a line.
fn grapheme_vis_width_at_vis_pos(g: &str, pos: usize, tab_width: usize) -> usize {
match g {
"\t" => {
let ending_pos = ((pos / tab_width) + 1) * tab_width;
return ending_pos - pos;
},
_ => {
if is_line_ending(g) {
return 1;
}
else {
return g.width(true);
}
}
}
}