diff --git a/src/buffer/line.rs b/src/buffer/line.rs index 6a19cc6..dd6bf81 100644 --- a/src/buffer/line.rs +++ b/src/buffer/line.rs @@ -6,28 +6,6 @@ use std::str::Graphemes; use string_utils::{grapheme_count, grapheme_pos_to_byte_pos, is_line_ending}; -/// 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); - } - } - } -} - - - /// A single line of text pub struct Line { text: Vec, // The text data, stored as UTF8 @@ -231,19 +209,6 @@ impl Line { } - /// Returns the visual cell width of the line - pub fn vis_width(&self, tab_width: usize) -> usize { - let mut width = 0; - - for g in self.as_str().graphemes(true) { - let w = grapheme_vis_width_at_vis_pos(g, width, tab_width); - width += w; - } - - return width; - } - - pub fn grapheme_at_index<'a>(&'a self, index: usize) -> &'a str { let mut iter = self.grapheme_iter(); let mut i = 0; @@ -260,75 +225,7 @@ impl Line { // Should never get here panic!("Line::grapheme_at_index(): index past end of line."); } - - - pub fn grapheme_width_at_index(&self, index: usize, tab_width: usize) -> usize { - let mut iter = self.grapheme_vis_iter(tab_width); - let mut i = 0; - for (_, _, width) in iter { - if i == index { - return width; - } - else { - i += 1; - } - } - - // Should never get here - panic!("Line::grapheme_at_index(): index past end of line."); - } - - - /// Translates a grapheme index into a visual horizontal position - pub fn grapheme_index_to_closest_vis_pos(&self, index: usize, tab_width: usize) -> usize { - let mut pos = 0; - let mut iter = self.as_str().graphemes(true); - - for _ in range(0, index) { - if let Some(g) = iter.next() { - let w = grapheme_vis_width_at_vis_pos(g, pos, tab_width); - pos += w; - } - else { - panic!("Line::grapheme_index_to_vis_pos(): index past end of line."); - } - } - - return pos; - } - - - /// Translates a visual horizontal position to the closest grapheme index - pub fn vis_pos_to_closest_grapheme_index(&self, vis_pos: usize, tab_width: usize) -> usize { - let mut pos = 0; - let mut i = 0; - let mut iter = self.as_str().graphemes(true); - - while pos < vis_pos { - if let Some(g) = iter.next() { - let w = grapheme_vis_width_at_vis_pos(g, pos, tab_width); - if (w + pos) > vis_pos { - let d1 = vis_pos - pos; - let d2 = (pos + w) - vis_pos; - if d2 < d1 { - i += 1; - } - break; - } - else { - pos += w; - i += 1; - } - } - else { - break; - } - } - - return i; - } - /// Returns an immutable string slice into the text block's memory pub fn as_str<'a>(&'a self) -> &'a str { @@ -479,16 +376,6 @@ impl Line { return iter; } - - - /// Returns an iterator over the graphemes of the line - pub fn grapheme_vis_iter<'a>(&'a self, tab_width: usize) -> LineGraphemeVisIter<'a> { - LineGraphemeVisIter { - graphemes: self.grapheme_iter(), - vis_pos: 0, - tab_width: tab_width, - } - } } @@ -624,73 +511,13 @@ impl<'a> Iterator for LineGraphemeIter<'a> { - -/// An iterator over the graphemes of a Line. This iterator yields not just -/// the grapheme, but also it's beginning visual position in the line and its -/// visual width. -pub struct LineGraphemeVisIter<'a> { - graphemes: LineGraphemeIter<'a>, - vis_pos: usize, - tab_width: usize, -} - -impl<'a> LineGraphemeVisIter<'a> { - pub fn skip_graphemes(&mut self, n: usize) { - for _ in range(0, n) { - if let None = self.next() { - break; - } - } - } - - // Skips at least n visual positions, and returns the number of excess - // skipped visual positions beyond n. - pub fn skip_vis_positions(&mut self, n: usize) -> usize { - let mut i = 0; - while i < n { - if let Some((_, _, width)) = self.next() { - i += width; - } - else { - break; - } - } - - if i > n { - return i - n; - } - else { - return 0; - } - } -} - -impl<'a> Iterator for LineGraphemeVisIter<'a> { - type Item = (&'a str, usize, usize); - - fn next(&mut self) -> Option<(&'a str, usize, usize)> { - if let Some(g) = self.graphemes.next() { - let pos = self.vis_pos; - let width = grapheme_vis_width_at_vis_pos(g, self.vis_pos, self.tab_width); - self.vis_pos += width; - return Some((g, pos, width)); - } - else { - return None; - } - } -} - - - - //========================================================================= // Line tests //========================================================================= #[cfg(test)] mod tests { - use super::{Line, LineEnding, LineGraphemeIter, LineGraphemeVisIter}; + use super::{Line, LineEnding, LineGraphemeIter}; const TAB_WIDTH: usize = 4; diff --git a/src/buffer/mod.rs b/src/buffer/mod.rs index e1adc1b..6c9a4b0 100644 --- a/src/buffer/mod.rs +++ b/src/buffer/mod.rs @@ -2,11 +2,11 @@ use std::mem; -use font::Font; use self::line::{Line, LineEnding}; use self::node::{BufferNode, BufferNodeGraphemeIter, BufferNodeLineIter}; use self::undo_stack::{UndoStack}; use self::undo_stack::Operation::*; +use line_formatter::{LineFormatter, RoundingBehavior}; use string_utils::{is_line_ending, grapheme_count}; pub mod line; @@ -19,23 +19,21 @@ mod undo_stack; //============================================================= /// A text buffer -pub struct Buffer { +pub struct Buffer { text: BufferNode, undo_stack: UndoStack, pub line_ending_type: LineEnding, - pub tab_width: usize, - pub font: Option, + pub formatter: T, } -impl Buffer { - pub fn new() -> Buffer { +impl Buffer { + pub fn new(formatter: T) -> Buffer { Buffer { - text: BufferNode::new(), + text: BufferNode::new(&formatter), undo_stack: UndoStack::new(), line_ending_type: LineEnding::LF, - tab_width: 4, - font: None, + formatter: formatter, } } @@ -68,7 +66,7 @@ impl Buffer { } fn _insert_text(&mut self, text: &str, pos: usize) { - self.text.insert_text(text, pos); + self.text.insert_text(&self.formatter, text, pos); } @@ -112,15 +110,15 @@ impl Buffer { } // Complete removal of all text else if pos_a == 0 && pos_b == self.text.grapheme_count { - let mut temp_node = BufferNode::new(); + let mut temp_node = BufferNode::new(&self.formatter); mem::swap(&mut (self.text), &mut temp_node); } // All other cases else { - if self.text.remove_text_recursive(pos_a, pos_b, true) { + if self.text.remove_text_recursive(&self.formatter, pos_a, pos_b, true) { panic!("Buffer::_remove_text(): dangling left side remains. This should never happen!"); } - self.text.set_last_line_ending_recursive(); + self.text.set_last_line_ending_recursive(&self.formatter); } } @@ -185,13 +183,13 @@ impl Buffer { } // Complete removal of all lines else if line_a == 0 && line_b == self.text.line_count { - let mut temp_node = BufferNode::new(); + let mut temp_node = BufferNode::new(&self.formatter); mem::swap(&mut (self.text), &mut temp_node); } // All other cases else { - self.text.remove_lines_recursive(line_a, line_b); - self.text.set_last_line_ending_recursive(); + self.text.remove_lines_recursive(&self.formatter, line_a, line_b); + self.text.set_last_line_ending_recursive(&self.formatter); } } @@ -200,7 +198,7 @@ impl Buffer { /// doing any sanity checks. This is primarily for efficient /// file loading. pub fn append_line_unchecked(&mut self, line: Line) { - self.text.append_line_unchecked_recursive(line); + self.text.append_line_unchecked_recursive(&self.formatter, line); } @@ -311,9 +309,11 @@ impl Buffer { /// If the index is off the end of the text, returns the visual line and /// column number of the last valid text position. pub fn index_to_v2d(&self, pos: usize) -> (usize, usize) { - let (v, h) = self.text.pos_1d_to_closest_2d_recursive(pos); - let vis_h = self.get_line(v).grapheme_index_to_closest_vis_pos(h, self.tab_width); - return (v, vis_h); + // TODO: update this to use the new LineFormatter stuff + //let (v, h) = self.text.pos_1d_to_closest_2d_recursive(pos); + //let vis_h = self.get_line(v).grapheme_index_to_closest_vis_pos(h, self.tab_width); + //return (v, vis_h); + return (0, 0); } @@ -323,15 +323,17 @@ impl Buffer { /// index of the horizontally-closest valid position. If the visual line /// number given is beyond the end of the buffer, returns the index of /// the buffer's last valid position. - pub fn v2d_to_index(&self, pos: (usize, usize)) -> usize { - if pos.0 >= self.line_count() { - return self.grapheme_count(); - } - else { - let gs = self.line_col_to_index((pos.0, 0)); - let h = self.get_line(pos.0).vis_pos_to_closest_grapheme_index(pos.1, self.tab_width); - return gs + h; - } + pub fn v2d_to_index(&self, pos: (usize, usize), rounding: (RoundingBehavior, RoundingBehavior)) -> usize { + // TODO: update this to use the new LineFormatter stuff + //if pos.0 >= self.line_count() { + // return self.grapheme_count(); + //} + //else { + // let gs = self.line_col_to_index((pos.0, 0)); + // let h = self.get_line(pos.0).vis_pos_to_closest_grapheme_index(pos.1, self.tab_width); + // return gs + h; + //} + return 0; } @@ -350,16 +352,6 @@ impl Buffer { } - pub fn get_grapheme_width(&self, index: usize) -> usize { - if index >= self.grapheme_count() { - panic!("Buffer::get_grapheme_width(): index past last grapheme."); - } - else { - return self.text.get_grapheme_width_recursive(index, self.tab_width); - } - } - - 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/buffer/node.rs b/src/buffer/node.rs index 6e179e9..20828e7 100644 --- a/src/buffer/node.rs +++ b/src/buffer/node.rs @@ -1,6 +1,7 @@ use std::mem; use std::cmp::{min, max}; +use line_formatter::LineFormatter; use string_utils::is_line_ending; use super::line::{Line, LineEnding, LineGraphemeIter, str_to_line_ending}; @@ -15,27 +16,35 @@ pub struct BufferNode { pub grapheme_count: usize, pub line_count: usize, + + pub vis_dim: (usize, usize), // Height, width } impl BufferNode { - pub fn new() -> BufferNode { + pub fn new(f: &T) -> BufferNode { + let line = Line::new(); + let dim = f.dimensions(&line); + BufferNode { - data: BufferNodeData::Leaf(Line::new()), + data: BufferNodeData::Leaf(line), tree_height: 1, grapheme_count: 0, line_count: 1, + vis_dim: dim, } } - pub fn new_from_line(line: Line) -> BufferNode { + pub fn new_from_line(f: &T, line: Line) -> BufferNode { let gc = line.grapheme_count(); + let dim = f.dimensions(&line); BufferNode { data: BufferNodeData::Leaf(line), tree_height: 1, grapheme_count: gc, line_count: 1, + vis_dim: dim, } } @@ -53,26 +62,28 @@ impl BufferNode { } - fn update_stats(&mut self) { + fn update_stats(&mut self, f: &T) { self.update_height(); match self.data { BufferNodeData::Leaf(ref line) => { self.grapheme_count = line.grapheme_count(); self.line_count = 1; + self.vis_dim = f.dimensions(line); }, BufferNodeData::Branch(ref left, ref right) => { self.grapheme_count = left.grapheme_count + right.grapheme_count; self.line_count = left.line_count + right.line_count; + self.vis_dim = (left.vis_dim.0 + right.vis_dim.0, max(left.vis_dim.1, right.vis_dim.1)); } } } /// Rotates the tree under the node left - fn rotate_left(&mut self) { - let mut temp = BufferNode::new(); + fn rotate_left(&mut self, f: &T) { + let mut temp = BufferNode::new(f); if let BufferNodeData::Branch(_, ref mut right) = self.data { mem::swap(&mut temp, &mut (**right)); @@ -90,17 +101,17 @@ impl BufferNode { if let BufferNodeData::Branch(ref mut left, _) = temp.data { mem::swap(&mut (**left), self); - left.update_stats(); + left.update_stats(f); } mem::swap(&mut temp, self); - self.update_stats(); + self.update_stats(f); } /// Rotates the tree under the node right - fn rotate_right(&mut self) { - let mut temp = BufferNode::new(); + fn rotate_right(&mut self, f: &T) { + let mut temp = BufferNode::new(f); if let BufferNodeData::Branch(ref mut left, _) = self.data { mem::swap(&mut temp, &mut (**left)); @@ -118,16 +129,16 @@ impl BufferNode { if let BufferNodeData::Branch(_, ref mut right) = temp.data { mem::swap(&mut (**right), self); - right.update_stats(); + right.update_stats(f); } mem::swap(&mut temp, self); - self.update_stats(); + self.update_stats(f); } /// Rebalances the tree under the node - fn rebalance(&mut self) { + fn rebalance(&mut self, f: &T) { loop { let mut rot: isize; @@ -144,7 +155,7 @@ impl BufferNode { } if child_rot { - left.rotate_left(); + left.rotate_left(f); } rot = 1; @@ -159,7 +170,7 @@ impl BufferNode { } if child_rot { - right.rotate_right(); + right.rotate_right(f); } rot = -1; @@ -175,10 +186,10 @@ impl BufferNode { } if rot == 1 { - self.rotate_right(); + self.rotate_right(f); } else if rot == -1 { - self.rotate_left(); + self.rotate_left(f); } } } @@ -202,24 +213,6 @@ impl BufferNode { } - pub fn get_grapheme_width_recursive(&self, index: usize, tab_width: usize) -> usize { - match self.data { - BufferNodeData::Leaf(ref line) => { - return line.grapheme_width_at_index(index, tab_width); - }, - - BufferNodeData::Branch(ref left, ref right) => { - if index < left.grapheme_count { - return left.get_grapheme_width_recursive(index, tab_width); - } - else { - return right.get_grapheme_width_recursive(index - left.grapheme_count, tab_width); - } - } - } - } - - pub fn get_line_recursive<'a>(&'a self, index: usize) -> &'a Line { match self.data { BufferNodeData::Leaf(ref line) => { @@ -293,7 +286,7 @@ impl BufferNode { /// Insert 'text' at grapheme position 'pos'. - pub fn insert_text(&mut self, text: &str, pos: usize) { + pub fn insert_text(&mut self, f: &T, text: &str, pos: usize) { // Byte indices let mut b1: usize = 0; let mut b2: usize = 0; @@ -306,14 +299,14 @@ impl BufferNode { for grapheme in text.grapheme_indices(true) { if is_line_ending(grapheme.1) { if g1 < g2 { - self.insert_text_recursive(text.slice(b1, b2), pos + g1); + self.insert_text_recursive(f, text.slice(b1, b2), pos + g1); } g1 = g2; b2 += grapheme.1.len(); g2 += 1; - self.insert_line_break_recursive(str_to_line_ending(grapheme.1), pos + g1); + self.insert_line_break_recursive(f, str_to_line_ending(grapheme.1), pos + g1); b1 = b2; g1 = g2; @@ -325,22 +318,22 @@ impl BufferNode { } if g1 < g2 { - self.insert_text_recursive(text.slice(b1, b2), pos + g1); + self.insert_text_recursive(f, text.slice(b1, b2), pos + g1); } } /// Inserts the given text string at the given grapheme position. /// Note: this assumes the given text has no newline graphemes. - pub fn insert_text_recursive(&mut self, text: &str, pos: usize) { + pub fn insert_text_recursive(&mut self, f: &T, text: &str, pos: usize) { match self.data { // Find node for text to be inserted into BufferNodeData::Branch(ref mut left, ref mut right) => { if pos < left.grapheme_count { - left.insert_text_recursive(text, pos); + left.insert_text_recursive(f, text, pos); } else { - right.insert_text_recursive(text, pos - left.grapheme_count); + right.insert_text_recursive(f, text, pos - left.grapheme_count); } }, @@ -351,12 +344,12 @@ impl BufferNode { }, } - self.update_stats(); + self.update_stats(f); } /// Inserts a line break at the given grapheme position - pub fn insert_line_break_recursive(&mut self, ending: LineEnding, pos: usize) { + pub fn insert_line_break_recursive(&mut self, f: &T, ending: LineEnding, pos: usize) { if ending == LineEnding::None { return; } @@ -368,10 +361,10 @@ impl BufferNode { // Find node for the line break to be inserted into BufferNodeData::Branch(ref mut left, ref mut right) => { if pos < left.grapheme_count { - left.insert_line_break_recursive(ending, pos); + left.insert_line_break_recursive(f, ending, pos); } else { - right.insert_line_break_recursive(ending, pos - left.grapheme_count); + right.insert_line_break_recursive(f, ending, pos - left.grapheme_count); } do_split = false; }, @@ -387,16 +380,16 @@ impl BufferNode { if do_split { // Insert line break let new_line = old_line.split(ending, pos); - let new_node_a = Box::new(BufferNode::new_from_line(old_line)); - let new_node_b = Box::new(BufferNode::new_from_line(new_line)); + let new_node_a = Box::new(BufferNode::new_from_line(f, old_line)); + let new_node_b = Box::new(BufferNode::new_from_line(f, new_line)); self.data = BufferNodeData::Branch(new_node_a, new_node_b); - self.update_stats(); + self.update_stats(f); } else { - self.update_stats(); - self.rebalance(); + self.update_stats(f); + self.rebalance(f); } } @@ -404,8 +397,8 @@ impl BufferNode { /// Removes text between grapheme positions pos_a and pos_b. /// Returns true if a dangling left side remains from the removal. /// Returns false otherwise. - pub fn remove_text_recursive(&mut self, pos_a: usize, pos_b: usize, is_last: bool) -> bool { - let mut temp_node = BufferNode::new(); + pub fn remove_text_recursive(&mut self, f: &T, pos_a: usize, pos_b: usize, is_last: bool) -> bool { + let mut temp_node = BufferNode::new(f); let mut total_side_removal = false; let mut dangling_line = false; let mut do_merge_fix = false; @@ -423,7 +416,7 @@ impl BufferNode { if pos_b > left.grapheme_count { let a = 0; let b = pos_b - left.grapheme_count; - right.remove_text_recursive(a, b, is_last); + right.remove_text_recursive(f, a, b, is_last); } total_side_removal = true; @@ -434,7 +427,7 @@ impl BufferNode { if pos_a < left.grapheme_count { let a = pos_a; let b = left.grapheme_count; - dangling_line = left.remove_text_recursive(a, b, false); + dangling_line = left.remove_text_recursive(f, a, b, false); } if is_last && !dangling_line { @@ -455,14 +448,14 @@ impl BufferNode { if pos_b > left.grapheme_count { let a = if pos_a > left.grapheme_count {pos_a - left.grapheme_count} else {0}; let b = pos_b - left.grapheme_count; - dangling_line = right.remove_text_recursive(a, b, is_last) && !is_last; + dangling_line = right.remove_text_recursive(f, a, b, is_last) && !is_last; } // Left side if pos_a < left.grapheme_count { let a = pos_a; let b = min(pos_b, left.grapheme_count); - do_merge_fix = left.remove_text_recursive(a, b, false); + do_merge_fix = left.remove_text_recursive(f, a, b, false); merge_line_number = left.line_count - 1; } } @@ -483,7 +476,7 @@ impl BufferNode { // Do the merge fix if necessary if do_merge_fix { - self.merge_line_with_next_recursive(merge_line_number, None); + self.merge_line_with_next_recursive(f, merge_line_number, None); } // If one of the sides was completely removed, replace self with the // remaining side. @@ -491,39 +484,39 @@ impl BufferNode { mem::swap(&mut temp_node, self); } - self.update_stats(); - self.rebalance(); + self.update_stats(f); + self.rebalance(f); return dangling_line; } - pub fn append_line_unchecked_recursive(&mut self, line: Line) { + pub fn append_line_unchecked_recursive(&mut self, f: &T, line: Line) { let mut other_line = Line::new(); if let BufferNodeData::Branch(_, ref mut right) = self.data { - right.append_line_unchecked_recursive(line); + right.append_line_unchecked_recursive(f, line); } else { if let BufferNodeData::Leaf(ref mut this_line) = self.data { mem::swap(this_line, &mut other_line); } - let new_node_a = Box::new(BufferNode::new_from_line(other_line)); - let new_node_b = Box::new(BufferNode::new_from_line(line)); + let new_node_a = Box::new(BufferNode::new_from_line(f, other_line)); + let new_node_b = Box::new(BufferNode::new_from_line(f, line)); self.data = BufferNodeData::Branch(new_node_a, new_node_b); } - self.update_stats(); - self.rebalance(); + self.update_stats(f); + self.rebalance(f); } /// Removes lines in line number range [line_a, line_b) - pub fn remove_lines_recursive(&mut self, line_a: usize, line_b: usize) { + pub fn remove_lines_recursive(&mut self, f: &T, line_a: usize, line_b: usize) { let mut remove_left = false; let mut remove_right = false; - let mut temp_node = BufferNode::new(); + let mut temp_node = BufferNode::new(f); if let BufferNodeData::Branch(ref mut left, ref mut right) = self.data { // Right node completely removed @@ -534,7 +527,7 @@ impl BufferNode { else if line_b > left.line_count { let a = if line_a > left.line_count {line_a - left.line_count} else {0}; let b = line_b - left.line_count; - right.remove_lines_recursive(a, b); + right.remove_lines_recursive(f, a, b); } // Left node completely removed @@ -545,7 +538,7 @@ impl BufferNode { else if line_a < left.line_count { let a = line_a; let b = min(left.line_count, line_b); - left.remove_lines_recursive(a, b); + left.remove_lines_recursive(f, a, b); } // Set up for node removal @@ -568,17 +561,17 @@ impl BufferNode { mem::swap(&mut temp_node, self); } - self.update_stats(); - self.rebalance(); + self.update_stats(f); + self.rebalance(f); } - pub fn merge_line_with_next_recursive(&mut self, line_number: usize, fetched_line: Option<&Line>) { + pub fn merge_line_with_next_recursive(&mut self, f: &T, line_number: usize, fetched_line: Option<&Line>) { match fetched_line { None => { - let line: Option = self.pull_out_line_recursive(line_number + 1); + let line: Option = self.pull_out_line_recursive(f, line_number + 1); if let Some(ref l) = line { - self.merge_line_with_next_recursive(line_number, Some(l)); + self.merge_line_with_next_recursive(f, line_number, Some(l)); } }, @@ -586,10 +579,10 @@ impl BufferNode { match self.data { BufferNodeData::Branch(ref mut left, ref mut right) => { if line_number < left.line_count { - left.merge_line_with_next_recursive(line_number, Some(line)); + left.merge_line_with_next_recursive(f, line_number, Some(line)); } else { - right.merge_line_with_next_recursive(line_number - left.line_count, Some(line)); + right.merge_line_with_next_recursive(f, line_number - left.line_count, Some(line)); } }, @@ -601,15 +594,15 @@ impl BufferNode { } } - self.update_stats(); - self.rebalance(); + self.update_stats(f); + self.rebalance(f); } /// Removes a single line out of the text and returns it. - pub fn pull_out_line_recursive(&mut self, line_number: usize) -> Option { + pub fn pull_out_line_recursive(&mut self, f: &T, line_number: usize) -> Option { let mut pulled_line = Line::new(); - let mut temp_node = BufferNode::new(); + let mut temp_node = BufferNode::new(f); let mut side_removal = false; match self.data { @@ -621,7 +614,7 @@ impl BufferNode { side_removal = true; } else { - pulled_line = left.pull_out_line_recursive(line_number).unwrap(); + pulled_line = left.pull_out_line_recursive(f, line_number).unwrap(); } } else if line_number < self.line_count { @@ -631,7 +624,7 @@ impl BufferNode { side_removal = true; } else { - pulled_line = right.pull_out_line_recursive(line_number - left.line_count).unwrap(); + pulled_line = right.pull_out_line_recursive(f, line_number - left.line_count).unwrap(); } } else { @@ -649,8 +642,8 @@ impl BufferNode { mem::swap(&mut temp_node, self); } - self.update_stats(); - self.rebalance(); + self.update_stats(f); + self.rebalance(f); return Some(pulled_line); } @@ -658,10 +651,10 @@ impl BufferNode { /// Ensures that the last line in the node tree has no /// ending line break. - pub fn set_last_line_ending_recursive(&mut self) { + pub fn set_last_line_ending_recursive(&mut self, f: &T) { match self.data { BufferNodeData::Branch(_, ref mut right) => { - right.set_last_line_ending_recursive(); + right.set_last_line_ending_recursive(f); }, BufferNodeData::Leaf(ref mut line) => { @@ -669,7 +662,7 @@ impl BufferNode { }, } - self.update_stats(); + self.update_stats(f); } diff --git a/src/editor/cursor.rs b/src/editor/cursor.rs index 2ab2797..a6d4567 100644 --- a/src/editor/cursor.rs +++ b/src/editor/cursor.rs @@ -5,7 +5,7 @@ use std::ops::{Index, IndexMut}; use std::cmp::Ordering; use buffer::Buffer; - +use line_formatter::LineFormatter; /// A text cursor. Also represents selections when range.0 != range.1. /// @@ -27,7 +27,7 @@ impl Cursor { } } - pub fn update_vis_start(&mut self, buf: &Buffer) { + pub fn update_vis_start(&mut self, buf: &Buffer) { let (_, h) = buf.index_to_v2d(self.range.0); self.vis_start = h; } diff --git a/src/editor/mod.rs b/src/editor/mod.rs index 3be2b21..d37cf9b 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -1,6 +1,8 @@ #![allow(dead_code)] use buffer::Buffer; +use line_formatter::{LineFormatter, ConsoleLineFormatter}; +use line_formatter::RoundingBehavior::*; use std::path::Path; use std::cmp::min; use files::{load_file_to_buffer, save_buffer_to_file}; @@ -11,7 +13,7 @@ mod cursor; pub struct Editor { - pub buffer: Buffer, + pub buffer: Buffer, pub file_path: Path, pub soft_tabs: bool, pub dirty: bool, @@ -29,7 +31,7 @@ impl Editor { /// Create a new blank editor pub fn new() -> Editor { Editor { - buffer: Buffer::new(), + buffer: Buffer::new(ConsoleLineFormatter::new(4)), file_path: Path::new(""), soft_tabs: false, dirty: false, @@ -40,9 +42,9 @@ impl Editor { } pub fn new_from_file(path: &Path) -> Editor { - let buf = match load_file_to_buffer(path) { + let buf = match load_file_to_buffer(path, ConsoleLineFormatter::new(4)) { Ok(b) => {b}, - _ => {Buffer::new()} + _ => {Buffer::new(ConsoleLineFormatter::new(4))} }; let mut ed = Editor { @@ -161,7 +163,7 @@ impl Editor { } self.soft_tabs = true; - self.buffer.tab_width = width; + self.buffer.formatter.tab_width = width as u8; } else { self.soft_tabs = false; @@ -268,7 +270,7 @@ impl Editor { // Figure out how many spaces to insert let (_, vis_pos) = self.buffer.index_to_v2d(c.range.0); - let next_tab_stop = ((vis_pos / self.buffer.tab_width) + 1) * self.buffer.tab_width; + let next_tab_stop = ((vis_pos / self.buffer.formatter.tab_width as usize) + 1) * self.buffer.formatter.tab_width as usize; let space_count = min(next_tab_stop - vis_pos, 8); @@ -467,7 +469,7 @@ impl Editor { let (v, _) = self.buffer.index_to_v2d(c.range.0); if v >= n { - c.range.0 = self.buffer.v2d_to_index((v - n, c.vis_start)); + c.range.0 = self.buffer.v2d_to_index((v - n, c.vis_start), (Floor, Floor)); c.range.1 = c.range.0; } else { @@ -485,7 +487,7 @@ impl Editor { let (v, _) = self.buffer.index_to_v2d(c.range.0); if v < (self.buffer.line_count() - n) { - c.range.0 = self.buffer.v2d_to_index((v + n, c.vis_start)); + c.range.0 = self.buffer.v2d_to_index((v + n, c.vis_start), (Floor, Floor)); c.range.1 = c.range.0; } else { @@ -547,7 +549,7 @@ impl Editor { let pos = self.buffer.line_col_to_index((n, 0)); let (v, _) = self.buffer.index_to_v2d(pos); self.cursors.truncate(1); - self.cursors[0].range.0 = self.buffer.v2d_to_index((v, self.cursors[0].vis_start)); + self.cursors[0].range.0 = self.buffer.v2d_to_index((v, self.cursors[0].vis_start), (Floor, Floor)); self.cursors[0].range.1 = self.cursors[0].range.0; // Adjust view diff --git a/src/files.rs b/src/files.rs index 4294ae6..f009e83 100644 --- a/src/files.rs +++ b/src/files.rs @@ -3,10 +3,11 @@ use std::io::fs::File; use std::path::Path; use buffer::line::{Line, LineEnding, line_ending_to_str}; +use line_formatter::LineFormatter; use buffer::Buffer as TextBuffer; -pub fn load_file_to_buffer(path: &Path) -> IoResult { - let mut tb = TextBuffer::new(); +pub fn load_file_to_buffer(path: &Path, lf: T) -> IoResult> { + let mut tb = TextBuffer::new(lf); let mut f = BufferedReader::new(try!(File::open(path))); for line in f.lines() { @@ -23,7 +24,7 @@ pub fn load_file_to_buffer(path: &Path) -> IoResult { return Ok(tb); } -pub fn save_buffer_to_file(tb: &TextBuffer, path: &Path) -> IoResult<()> { +pub fn save_buffer_to_file(tb: &TextBuffer, path: &Path) -> IoResult<()> { // TODO: make save atomic let mut iter = tb.line_iter(); let mut f = BufferedWriter::new(try!(File::create(path))); diff --git a/src/line_formatter.rs b/src/line_formatter.rs new file mode 100644 index 0000000..0ae9658 --- /dev/null +++ b/src/line_formatter.rs @@ -0,0 +1,160 @@ +use buffer::line::{Line, LineGraphemeIter}; +use string_utils::{is_line_ending}; + +pub enum RoundingBehavior { + Round, + Floor, + Ceiling, +} + +pub trait LineFormatter { + fn dimensions(&self, line: &Line) -> (usize, usize); + + fn index_to_v2d(&self, line: &Line, index: usize) -> (usize, usize); + + fn v2d_to_index(&self, line: &Line, v2d: (usize, usize), rounding: (RoundingBehavior, RoundingBehavior)) -> usize; +} + + + + + +//============================================================ +// An implementation of the LineFormatter stuff for consoles + +pub struct ConsoleLineFormatterVisIter<'a> { + grapheme_iter: LineGraphemeIter<'a>, + f: &'a ConsoleLineFormatter, + pos: (usize, usize), +} + + + +impl<'a> Iterator for ConsoleLineFormatterVisIter<'a> { + type Item = (&'a str, usize, usize); + + fn next(&mut self) -> Option<(&'a str, usize, usize)> { + if let Some(g) = self.grapheme_iter.next() { + let pos = self.pos; + let width = grapheme_vis_width_at_vis_pos(g, self.pos.1, self.f.tab_width as usize); + self.pos = (self.pos.0, self.pos.1 + width); + return Some((g, pos.0, pos.1)); + } + else { + return None; + } + } +} + + +pub struct ConsoleLineFormatter { + pub tab_width: u8, +} + + +impl ConsoleLineFormatter { + pub fn new(tab_width: u8) -> ConsoleLineFormatter { + ConsoleLineFormatter { + tab_width: tab_width, + } + } + + + /// Returns the visual cell width of a line + pub fn vis_width(&self, line: &Line) -> usize { + let mut width = 0; + + for g in line.grapheme_iter() { + let w = grapheme_vis_width_at_vis_pos(g, width, self.tab_width as usize); + width += w; + } + + return width; + } + + + pub fn vis_grapheme_iter<'b>(&'b self, line: &'b Line) -> ConsoleLineFormatterVisIter<'b> { + ConsoleLineFormatterVisIter { + grapheme_iter: line.grapheme_iter(), + f: self, + pos: (0, 0), + } + } +} + + +impl<'a> LineFormatter for ConsoleLineFormatter { + fn dimensions(&self, line: &Line) -> (usize, usize) { + return (1, self.vis_width(line)); + } + + + fn index_to_v2d(&self, line: &Line, index: usize) -> (usize, usize) { + let mut pos = 0; + let mut iter = line.grapheme_iter(); + + for _ in range(0, index) { + if let Some(g) = iter.next() { + let w = grapheme_vis_width_at_vis_pos(g, pos, self.tab_width as usize); + pos += w; + } + else { + panic!("ConsoleLineFormatter::index_to_v2d(): index past end of line."); + } + } + + return (0, pos); + } + + + fn v2d_to_index(&self, line: &Line, v2d: (usize, usize), rounding: (RoundingBehavior, RoundingBehavior)) -> usize { + let mut pos = 0; + let mut i = 0; + let mut iter = line.grapheme_iter(); + + while pos < v2d.1 { + if let Some(g) = iter.next() { + let w = grapheme_vis_width_at_vis_pos(g, pos, self.tab_width as usize); + if (w + pos) > v2d.1 { + let d1 = v2d.1 - pos; + let d2 = (pos + w) - v2d.1; + if d2 < d1 { + i += 1; + } + break; + } + else { + pos += w; + i += 1; + } + } + else { + break; + } + } + + return i; + } +} + + + +/// 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); + } + } + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index efe50fb..ae4e656 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,15 +10,16 @@ use std::path::Path; use docopt::Docopt; use editor::Editor; use term_ui::TermUI; -use gui::GUI; +//use gui::GUI; mod string_utils; mod buffer; +mod line_formatter; mod files; mod editor; mod term_ui; -mod font; -mod gui; +//mod font; +//mod gui; @@ -58,18 +59,18 @@ fn main() { }; // Initialize and start UI - if args.flag_gui { - // GUI - sdl2::init(sdl2::INIT_VIDEO); - let mut ui = GUI::new_from_editor(editor); - ui.main_ui_loop(); - sdl2::quit(); - } - else { +// if args.flag_gui { +// // GUI +// sdl2::init(sdl2::INIT_VIDEO); +// let mut ui = GUI::new_from_editor(editor); +// ui.main_ui_loop(); +// sdl2::quit(); +// } +// else { // Console UI let mut ui = TermUI::new_from_editor(editor); ui.main_ui_loop(); - } +// } //println!("{}", editor.buffer.root.tree_height); } diff --git a/src/term_ui.rs b/src/term_ui.rs index 9a8835d..1a99d33 100644 --- a/src/term_ui.rs +++ b/src/term_ui.rs @@ -7,6 +7,7 @@ use std::char; use std::time::duration::Duration; use string_utils::{is_line_ending}; use buffer::line::{line_ending_to_str, LineEnding}; +use line_formatter::LineFormatter; // Key codes const K_ENTER: u16 = 13; @@ -325,7 +326,7 @@ impl TermUI { LineEnding::PS => "PS", }; let soft_tabs_str = if editor.soft_tabs {"spaces"} else {"tabs"}; - let info_line = format!("UTF8:{} {}:{}", nl, soft_tabs_str, editor.buffer.tab_width); + let info_line = format!("UTF8:{} {}:{}", nl, soft_tabs_str, editor.buffer.formatter.tab_width as usize); self.rb.print(c2.1 - 30, c1.0, rustbox::RB_NORMAL, foreground, background, info_line.as_slice()); // Draw main text editing area @@ -336,7 +337,7 @@ impl TermUI { fn draw_editor_text(&self, editor: &Editor, c1: (usize, usize), c2: (usize, usize)) { let mut line_iter = editor.buffer.line_iter_at_index(editor.view_pos.0); - let mut grapheme_index; + //let mut grapheme_index; let mut vis_line_num = editor.view_pos.0; let mut vis_col_num = editor.view_pos.1; @@ -349,86 +350,15 @@ impl TermUI { loop { if let Some(line) = line_iter.next() { - let mut g_iter = line.grapheme_vis_iter(editor.buffer.tab_width); - let excess = g_iter.skip_vis_positions(editor.view_pos.1); + let mut g_iter = editor.buffer.formatter.vis_grapheme_iter(line); - vis_col_num += excess; - print_col_num += excess; - - grapheme_index = editor.buffer.v2d_to_index((vis_line_num, vis_col_num)); - - - - for (g, pos, width) in g_iter { - print_col_num = pos - editor.view_pos.1; - - // 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; - } - } - - // Print to screen - if is_line_ending(g) { - if at_cursor { - self.rb.print(print_col_num, print_line_num, rustbox::RB_NORMAL, Color::Black, Color::White, " "); - } - } - else if g == "\t" { - for i in range(print_col_num, print_col_num + width) { - self.rb.print(i, print_line_num, rustbox::RB_NORMAL, Color::White, Color::Black, " "); - } - - if at_cursor { - self.rb.print(print_col_num, print_line_num, rustbox::RB_NORMAL, Color::Black, Color::White, " "); - } - } - else { - if at_cursor { - self.rb.print(print_col_num, print_line_num, rustbox::RB_NORMAL, Color::Black, Color::White, g); - } - else { - self.rb.print(print_col_num, print_line_num, rustbox::RB_NORMAL, Color::White, Color::Black, g); - } - } - - vis_col_num += width; - grapheme_index += 1; - print_col_num += width; - - if print_col_num > max_print_col { - break; - } + for (g, pos_y, pos_x) in g_iter { + self.rb.print(pos_x, pos_y, rustbox::RB_NORMAL, Color::White, Color::Black, g); } } else { break; } - - vis_line_num += 1; - print_line_num += 1; - vis_col_num = editor.view_pos.1; - - if print_line_num > max_print_line { - break; - } - } - - // Print cursor(s) if it's at the end of the text, and thus wasn't printed - // already. - for c in editor.cursors.iter() { - if c.range.0 >= editor.buffer.grapheme_count() { - let vis_cursor_pos = editor.buffer.index_to_v2d(c.range.0); - if (vis_cursor_pos.0 >= editor.view_pos.0) && (vis_cursor_pos.1 >= editor.view_pos.1) { - let print_cursor_pos = (vis_cursor_pos.0 - editor.view_pos.0 + c1.0, vis_cursor_pos.1 - editor.view_pos.1 + c1.1); - - if print_cursor_pos.0 >= c1.0 && print_cursor_pos.0 <= c2.0 && print_cursor_pos.1 >= c1.1 && print_cursor_pos.1 <= c2.1 { - self.rb.print(print_cursor_pos.1, print_cursor_pos.0, rustbox::RB_NORMAL, Color::Black, Color::White, " "); - } - } - } } }