diff --git a/src/buffer/mod.rs b/src/buffer/mod.rs index 90d14a7..7f7683d 100644 --- a/src/buffer/mod.rs +++ b/src/buffer/mod.rs @@ -375,7 +375,7 @@ impl Buffer { } - fn get_line<'a>(&'a self, index: usize) -> &'a Line { + pub fn get_line<'a>(&'a self, index: usize) -> &'a Line { if index >= self.line_count() { panic!("get_line(): index out of bounds."); } diff --git a/src/formatter.rs b/src/formatter.rs index 8b3da71..fdef5f5 100644 --- a/src/formatter.rs +++ b/src/formatter.rs @@ -1,5 +1,5 @@ use buffer::line::{Line, LineGraphemeIter}; -use std::cmp::min; +use std::cmp::{min, max}; #[derive(Copy, PartialEq)] pub enum RoundingBehavior { @@ -10,11 +10,67 @@ pub enum RoundingBehavior { pub trait LineFormatter<'a> { - type Iter: Iterator + 'a; + // The iterator yields the grapheme, the 2d position of the grapheme, and the grapheme's width + type Iter: Iterator + 'a; fn single_line_height(&self) -> usize; fn iter(&'a self, line: &'a Line) -> Self::Iter; + + + /// Returns the 2d visual dimensions of the given line when formatted + /// by the formatter. + fn dimensions(&'a self, line: &'a Line) -> (usize, usize) { + let mut dim: (usize, usize) = (0, 0); + + for (_, pos, width) in self.iter(line) { + dim = (max(dim.0, pos.0), max(dim.1, pos.1 + width)); + } + + dim.0 += self.single_line_height(); + + return dim; + } + + + /// Converts a grapheme index within a line into a visual 2d position. + fn index_to_v2d(&'a self, line: &'a Line, index: usize) -> (usize, usize) { + let mut pos = (0, 0); + let mut i = 0; + let mut last_width = 0; + + for (_, _pos, width) in self.iter(line) { + pos = _pos; + last_width = width; + i += 1; + + if i > index { + return pos; + } + } + + return (pos.0, pos.1 + last_width); + } + + + /// Converts a visual 2d position into a grapheme index within a line. + fn v2d_to_index(&'a self, line: &'a Line, v2d: (usize, usize), rounding: (RoundingBehavior, RoundingBehavior)) -> usize { + // TODO: handle rounding modes + let mut i = 0; + + for (_, pos, _) in self.iter(line) { + if pos.0 > v2d.0 { + break; + } + else if pos.0 == v2d.0 && pos.1 >= v2d.1 { + break; + } + + i += 1; + } + + return i; + } } @@ -31,13 +87,13 @@ pub struct TestLineFormatIter<'a> { } impl<'a> Iterator for TestLineFormatIter<'a> { - type Item = (&'a str, (usize, usize), (usize, usize)); + type Item = (&'a str, (usize, usize), usize); - fn next(&mut self) -> Option<(&'a str, (usize, usize), (usize, usize))> { + fn next(&mut self) -> Option<(&'a str, (usize, usize), usize)> { if let Some(g) = self.grapheme_iter.next() { let pos = self.pos; self.pos = (pos.0, pos.1 + 1); - return Some((g, pos, (1, self.f.tab_width as usize))); + return Some((g, pos, 1)); } else { return None; diff --git a/src/term_ui/formatter.rs b/src/term_ui/formatter.rs index d9f8ac2..094a794 100644 --- a/src/term_ui/formatter.rs +++ b/src/term_ui/formatter.rs @@ -54,21 +54,21 @@ pub struct ConsoleLineFormatterVisIter<'a> { impl<'a> Iterator for ConsoleLineFormatterVisIter<'a> { - type Item = (&'a str, (usize, usize), (usize, usize)); + type Item = (&'a str, (usize, usize), usize); - fn next(&mut self) -> Option<(&'a str, (usize, 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, (1, width))); + return Some((g, pos, width)); } else { let pos = self.pos; self.pos = (self.pos.0, self.pos.1 + width); - return Some((g, pos, (1, width))); + return Some((g, pos, width)); } } else { diff --git a/src/term_ui/mod.rs b/src/term_ui/mod.rs index 004b124..1057873 100644 --- a/src/term_ui/mod.rs +++ b/src/term_ui/mod.rs @@ -354,79 +354,76 @@ impl<'a> TermUI<'a> { fn draw_editor_text(&self, editor: &Editor, c1: (usize, usize), c2: (usize, usize)) { // TODO: update to new formatting code - //// Calculate all the starting info - //let editor_corner_index = editor.buffer.v2d_to_index(editor.view_pos, (RoundingBehavior::Floor, RoundingBehavior::Floor)); - //let (starting_line, _) = editor.buffer.index_to_line_col(editor_corner_index); - //let mut grapheme_index = editor.buffer.line_col_to_index((starting_line, 0)); - //let (vis_starting_line, _) = editor.buffer.index_to_v2d(grapheme_index); - // - //let mut screen_line = c1.0 as isize + vis_starting_line as isize; - //let screen_col = c1.1 as isize; - // - //let mut line_iter = editor.buffer.line_iter_at_index(starting_line); - // - //for line in line_iter { - // let mut g_iter = editor.buffer.formatter.vis_grapheme_iter(line); - // - // // Loop through the graphemes of the line and print them to - // // the screen. - // for (g, (pos_y, pos_x), width) in g_iter { - // // Calculate the cell coordinates at which to draw the grapheme - // let px = pos_x as isize + screen_col - editor.view_pos.1 as isize; - // let py = pos_y as isize + screen_line - editor.view_pos.0 as isize; - // - // // If we're off the bottom, we're done - // if py > c2.0 as isize { - // return; - // } - // - // // Draw the grapheme to the screen if it's in bounds - // if (px >= c1.1 as isize) && (py >= c1.0 as isize) && (px <= c2.1 as isize) { - // // Check if the character is within a cursor - // let mut at_cursor = false; - // for c in editor.cursors.iter() { - // if grapheme_index >= c.range.0 && grapheme_index <= c.range.1 { - // at_cursor = true; - // } - // } - // - // // Actually print the character - // if is_line_ending(g) { - // if at_cursor { - // self.rb.print(px as usize, py as usize, rustbox::RB_NORMAL, Color::Black, Color::White, " "); - // } - // } - // else if g == "\t" { - // for i in 0..width { - // let tpx = px as usize + i; - // if tpx <= c2.1 { - // self.rb.print(tpx as usize, py as usize, rustbox::RB_NORMAL, Color::White, Color::Black, " "); - // } - // } - // - // if at_cursor { - // self.rb.print(px as usize, py as usize, rustbox::RB_NORMAL, Color::Black, Color::White, " "); - // } - // } - // else { - // if at_cursor { - // self.rb.print(px as usize, py as usize, rustbox::RB_NORMAL, Color::Black, Color::White, g); - // } - // else { - // self.rb.print(px as usize, py as usize, rustbox::RB_NORMAL, Color::White, Color::Black, g); - // } - // } - // } - // - // grapheme_index += 1; - // } - // - // let (dim_y, _) = editor.buffer.formatter.dimensions(line); - // screen_line += dim_y as isize; - //} - // - // - // + // Calculate all the starting info + let (starting_line, _) = editor.buffer.index_to_line_col(editor.view_pos.0); + let mut grapheme_index = editor.buffer.line_col_to_index((starting_line, 0)); + let (vis_line_offset, _) = editor.formatter.index_to_v2d(editor.buffer.get_line(starting_line), editor.view_pos.0 - grapheme_index); + + let mut screen_line = c1.0 as isize - vis_line_offset as isize; + let screen_col = c1.1 as isize; + + let mut line_iter = editor.buffer.line_iter_at_index(starting_line); + for line in line_iter { + let mut g_iter = editor.formatter.iter(line); + + // Loop through the graphemes of the line and print them to + // the screen. + for (g, (pos_y, pos_x), width) in g_iter { + // Calculate the cell coordinates at which to draw the grapheme + let px = pos_x as isize + screen_col - editor.view_pos.1 as isize; + let py = pos_y as isize + screen_line; + + // If we're off the bottom, we're done + if py > c2.0 as isize { + return; + } + + // Draw the grapheme to the screen if it's in bounds + if (px >= c1.1 as isize) && (py >= c1.0 as isize) && (px <= c2.1 as isize) { + // Check if the character is within a cursor + let mut at_cursor = false; + for c in editor.cursors.iter() { + if grapheme_index >= c.range.0 && grapheme_index <= c.range.1 { + at_cursor = true; + } + } + + // Actually print the character + if is_line_ending(g) { + if at_cursor { + self.rb.print(px as usize, py as usize, rustbox::RB_NORMAL, Color::Black, Color::White, " "); + } + } + else if g == "\t" { + for i in 0..width { + let tpx = px as usize + i; + if tpx <= c2.1 { + self.rb.print(tpx as usize, py as usize, rustbox::RB_NORMAL, Color::White, Color::Black, " "); + } + } + + if at_cursor { + self.rb.print(px as usize, py as usize, rustbox::RB_NORMAL, Color::Black, Color::White, " "); + } + } + else { + if at_cursor { + self.rb.print(px as usize, py as usize, rustbox::RB_NORMAL, Color::Black, Color::White, g); + } + else { + self.rb.print(px as usize, py as usize, rustbox::RB_NORMAL, Color::White, Color::Black, g); + } + } + } + + grapheme_index += 1; + } + + let (dim_y, _) = editor.formatter.dimensions(line); + screen_line += dim_y as isize; + } + + //// If we get here, it means we reached the end of the text buffer //// without going off the bottom of the screen. So draw the cursor //// at the end if needed.