From c69ebc240fdf58ee3e5de262685c3d618a142919 Mon Sep 17 00:00:00 2001 From: Nathan Vegdahl Date: Sat, 24 Jan 2015 14:35:49 -0800 Subject: [PATCH] WIP: making Buffers so they can be given LineFormatters. A LineFormatter determines how a line of text is visually displayed in 2d space. This allows both the Console and GUI version of the editor to share the same buffer code whilst still handling the differing ways in which both handle visual display. --- src/buffer/line.rs | 175 +----------------------------------------- src/buffer/mod.rs | 70 ++++++++--------- src/buffer/node.rs | 169 +++++++++++++++++++--------------------- src/editor/cursor.rs | 4 +- src/editor/mod.rs | 20 ++--- src/files.rs | 7 +- src/line_formatter.rs | 160 ++++++++++++++++++++++++++++++++++++++ src/main.rs | 25 +++--- src/term_ui.rs | 82 ++------------------ 9 files changed, 309 insertions(+), 403 deletions(-) create mode 100644 src/line_formatter.rs 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, " "); - } - } - } } }