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.
This commit is contained in:
Nathan Vegdahl 2015-01-24 14:35:49 -08:00
parent ce17aa6a62
commit c69ebc240f
9 changed files with 309 additions and 403 deletions

View File

@ -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<u8>, // 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;

View File

@ -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<T: LineFormatter> {
text: BufferNode,
undo_stack: UndoStack,
pub line_ending_type: LineEnding,
pub tab_width: usize,
pub font: Option<Font>,
pub formatter: T,
}
impl Buffer {
pub fn new() -> Buffer {
impl<T: LineFormatter> Buffer<T> {
pub fn new(formatter: T) -> Buffer<T> {
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.");

View File

@ -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<T: LineFormatter>(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<T: LineFormatter>(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<T: LineFormatter>(&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<T: LineFormatter>(&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<T: LineFormatter>(&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<T: LineFormatter>(&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<T: LineFormatter>(&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<T: LineFormatter>(&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<T: LineFormatter>(&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<T: LineFormatter>(&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<T: LineFormatter>(&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<T: LineFormatter>(&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<T: LineFormatter>(&mut self, f: &T, line_number: usize, fetched_line: Option<&Line>) {
match fetched_line {
None => {
let line: Option<Line> = self.pull_out_line_recursive(line_number + 1);
let line: Option<Line> = 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<Line> {
pub fn pull_out_line_recursive<T: LineFormatter>(&mut self, f: &T, line_number: usize) -> Option<Line> {
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<T: LineFormatter>(&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);
}

View File

@ -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<T: LineFormatter>(&mut self, buf: &Buffer<T>) {
let (_, h) = buf.index_to_v2d(self.range.0);
self.vis_start = h;
}

View File

@ -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<ConsoleLineFormatter>,
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

View File

@ -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<TextBuffer> {
let mut tb = TextBuffer::new();
pub fn load_file_to_buffer<T: LineFormatter>(path: &Path, lf: T) -> IoResult<TextBuffer<T>> {
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<TextBuffer> {
return Ok(tb);
}
pub fn save_buffer_to_file(tb: &TextBuffer, path: &Path) -> IoResult<()> {
pub fn save_buffer_to_file<T: LineFormatter>(tb: &TextBuffer<T>, path: &Path) -> IoResult<()> {
// TODO: make save atomic
let mut iter = tb.line_iter();
let mut f = BufferedWriter::new(try!(File::create(path)));

160
src/line_formatter.rs Normal file
View File

@ -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);
}
}
}
}

View File

@ -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);
}

View File

@ -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, " ");
}
}
}
}
}