diff --git a/src/buffer/mod.rs b/src/buffer/mod.rs index 7f7683d..c4f30d5 100644 --- a/src/buffer/mod.rs +++ b/src/buffer/mod.rs @@ -1,7 +1,7 @@ #![allow(dead_code)] use std::mem; -use std::path::Path; +use std::old_path::Path; use std::old_io::fs::File; use std::old_io::{IoResult, BufferedReader}; @@ -485,22 +485,22 @@ impl<'a> BufferGraphemeIter<'a> { self.gi.skip_graphemes(n) } - pub fn skip_non_newline_graphemes(&mut self, n: usize) -> bool { - let mut i: usize = 0; - - for g in self.gi { - if is_line_ending(g) { - return true; - } - - i += 1; - if i >= n { - break; - } - } - - return false; - } + //pub fn skip_non_newline_graphemes(&mut self, n: usize) -> bool { + // let mut i: usize = 0; + // + // for g in self.gi { + // if is_line_ending(g) { + // return true; + // } + // + // i += 1; + // if i >= n { + // break; + // } + // } + // + // return false; + //} } diff --git a/src/editor/cursor.rs b/src/editor/cursor.rs index e91ce01..ea7fcc2 100644 --- a/src/editor/cursor.rs +++ b/src/editor/cursor.rs @@ -27,10 +27,8 @@ impl Cursor { } } - pub fn update_vis_start<'a, T: LineFormatter<'a>>(&mut self, buf: &Buffer, f: &T) { - // TODO - //let (_, h) = buf.index_to_v2d(self.range.0); - //self.vis_start = h; + pub fn update_vis_start(&mut self, buf: &Buffer, f: &T) { + self.vis_start = f.index_to_horizontal_v2d(buf, self.range.0); } } diff --git a/src/editor/mod.rs b/src/editor/mod.rs index af01aed..ab44069 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -4,7 +4,7 @@ use buffer::Buffer; use buffer::line::LineEnding; use formatter::LineFormatter; use formatter::RoundingBehavior::*; -use std::path::Path; +use std::old_path::Path; use std::cmp::{min, max}; use files::{save_buffer_to_file}; use string_utils::grapheme_count; @@ -13,7 +13,7 @@ use self::cursor::CursorSet; mod cursor; -pub struct Editor<'a, T: LineFormatter<'a>> { +pub struct Editor { pub buffer: Buffer, pub formatter: T, pub file_path: Path, @@ -31,9 +31,9 @@ pub struct Editor<'a, T: LineFormatter<'a>> { } -impl<'a, T: LineFormatter<'a>> Editor<'a, T> { +impl Editor { /// Create a new blank editor - pub fn new(formatter: T) -> Editor<'a, T> { + pub fn new(formatter: T) -> Editor { Editor { buffer: Buffer::new(), formatter: formatter, @@ -300,10 +300,10 @@ impl<'a, T: LineFormatter<'a>> Editor<'a, T> { // there are no cursors currently in view, and should jump to // the closest cursor. - let gi = self.cursors[0].range.0; + //let gi = self.cursors[0].range.0; //let vho = self.cursors[0].vis_start; - self.view_pos.0 = gi; + //self.view_pos.0 = gi; // TODO: horizontal offset //self.view_pos.1 = vho; @@ -336,45 +336,43 @@ impl<'a, T: LineFormatter<'a>> Editor<'a, T> { pub fn insert_tab_at_cursor(&mut self) { - // TODO: update to new formatting code + self.cursors.make_consistent(); - //self.cursors.make_consistent(); - // - //if self.soft_tabs { - // let mut offset = 0; - // - // for c in self.cursors.iter_mut() { - // // Update cursor with offset - // c.range.0 += offset; - // c.range.1 += offset; - // - // // Figure out how many spaces to insert - // let (_, vis_pos) = self.buffer.index_to_v2d(c.range.0); - // // TODO: handle tab settings - // let next_tab_stop = ((vis_pos / self.soft_tab_width as usize) + 1) * self.soft_tab_width as usize; - // let space_count = min(next_tab_stop - vis_pos, 8); - // - // - // // Insert spaces - // let space_strs = ["", " ", " ", " ", " ", " ", " ", " ", " "]; - // self.buffer.insert_text(space_strs[space_count], c.range.0); - // self.dirty = true; - // - // // Move cursor - // c.range.0 += space_count; - // c.range.1 += space_count; - // c.update_vis_start(&(self.buffer), &(self.formatter)); - // - // // Update offset - // offset += space_count; - // } - // - // // Adjust view - // self.move_view_to_cursor(); - //} - //else { - // self.insert_text_at_cursor("\t"); - //} + if self.soft_tabs { + let mut offset = 0; + + for c in self.cursors.iter_mut() { + // Update cursor with offset + c.range.0 += offset; + c.range.1 += offset; + + // Figure out how many spaces to insert + let vis_pos = self.formatter.index_to_horizontal_v2d(&self.buffer, c.range.0); + // TODO: handle tab settings + let next_tab_stop = ((vis_pos / self.soft_tab_width as usize) + 1) * self.soft_tab_width as usize; + let space_count = min(next_tab_stop - vis_pos, 8); + + + // Insert spaces + let space_strs = ["", " ", " ", " ", " ", " ", " ", " ", " "]; + self.buffer.insert_text(space_strs[space_count], c.range.0); + self.dirty = true; + + // Move cursor + c.range.0 += space_count; + c.range.1 += space_count; + c.update_vis_start(&(self.buffer), &(self.formatter)); + + // Update offset + offset += space_count; + } + + // Adjust view + self.move_view_to_cursor(); + } + else { + self.insert_text_at_cursor("\t"); + } } @@ -556,48 +554,44 @@ impl<'a, T: LineFormatter<'a>> Editor<'a, T> { pub fn cursor_up(&mut self, n: usize) { - // TODO: update to new formatting code + for c in self.cursors.iter_mut() { + let vmove = -1 * (n * self.formatter.single_line_height()) as isize; + let mut temp_index = self.formatter.index_offset_vertical_v2d(&self.buffer, c.range.0, vmove, (Round, Round)); + + if temp_index == 0 { + c.update_vis_start(&(self.buffer), &(self.formatter)); + } + else { + temp_index = self.formatter.index_set_horizontal_v2d(&self.buffer, temp_index, c.vis_start, Round); + } + + c.range.0 = temp_index; + c.range.1 = temp_index; + } - //for c in self.cursors.iter_mut() { - // let vmove = n * self.buffer.formatter.single_line_height(); - // let (v, _) = self.buffer.index_to_v2d(c.range.0); - // - // if vmove <= v { - // c.range.0 = self.buffer.v2d_to_index((v - vmove, c.vis_start), (Floor, Floor)); - // c.range.1 = c.range.0; - // } - // else { - // c.range = (0, 0); - // c.update_vis_start(&(self.buffer), &(self.formatter)); - // } - //} - // - //// Adjust view - //self.move_view_to_cursor(); + // Adjust view + self.move_view_to_cursor(); } pub fn cursor_down(&mut self, n: usize) { - // TODO: update to new formatting code + for c in self.cursors.iter_mut() { + let vmove = (n * self.formatter.single_line_height()) as isize; + let mut temp_index = self.formatter.index_offset_vertical_v2d(&self.buffer, c.range.0, vmove, (Round, Round)); + + if temp_index == self.buffer.grapheme_count() { + c.update_vis_start(&(self.buffer), &(self.formatter)); + } + else { + temp_index = self.formatter.index_set_horizontal_v2d(&self.buffer, temp_index, c.vis_start, Round); + } + + c.range.0 = temp_index; + c.range.1 = temp_index; + } - //for c in self.cursors.iter_mut() { - // let vmove = n * self.buffer.formatter.single_line_height(); - // let (v, _) = self.buffer.index_to_v2d(c.range.0); - // let (h, _) = self.buffer.dimensions(); - // - // if vmove < (h - v) { - // c.range.0 = self.buffer.v2d_to_index((v + vmove, c.vis_start), (Floor, Floor)); - // c.range.1 = c.range.0; - // } - // else { - // let end = self.buffer.grapheme_count(); - // c.range = (end, end); - // c.update_vis_start(&(self.buffer), &(self.formatter)); - // } - //} - // - //// Adjust view - //self.move_view_to_cursor(); + // Adjust view + self.move_view_to_cursor(); } diff --git a/src/files.rs b/src/files.rs index b302fa1..2416581 100644 --- a/src/files.rs +++ b/src/files.rs @@ -1,6 +1,6 @@ use std::old_io::{IoResult, BufferedWriter}; use std::old_io::fs::File; -use std::path::Path; +use std::old_path::Path; use buffer::line::{line_ending_to_str}; use buffer::Buffer as TextBuffer; diff --git a/src/formatter.rs b/src/formatter.rs index 1328d9b..80735a5 100644 --- a/src/formatter.rs +++ b/src/formatter.rs @@ -1,6 +1,7 @@ #![allow(dead_code)] -use buffer::line::{Line, LineGraphemeIter}; +use buffer::line::Line; +use buffer::Buffer; use std::cmp::max; #[derive(Copy, PartialEq)] @@ -11,160 +12,178 @@ pub enum RoundingBehavior { } -pub trait LineFormatter<'a> { - // The iterator yields the grapheme, the 2d position of the grapheme, and the grapheme's width - type Iter: Iterator + 'a; - +pub trait LineFormatter { fn single_line_height(&self) -> usize; - - fn iter(&'a self, line: &'a Line) -> Self::Iter; - /// Returns the 2d visual dimensions of the given line when formatted /// by the formatter. - fn dimensions(&'a self, line: &'a Line) -> (usize, usize) { - let mut dim: (usize, usize) = (0, 0); - - for (_, pos, width) in self.iter(line) { - dim = (max(dim.0, pos.0), max(dim.1, pos.1 + width)); - } - - dim.0 += self.single_line_height(); - - return dim; - } + fn dimensions(&self, line: &Line) -> (usize, usize); /// Converts a grapheme index within a line into a visual 2d position. - fn index_to_v2d(&'a self, line: &'a Line, index: usize) -> (usize, usize) { - let mut pos = (0, 0); - let mut i = 0; - let mut last_width = 0; - - for (_, _pos, width) in self.iter(line) { - pos = _pos; - last_width = width; - i += 1; - - if i > index { - return pos; - } - } - - return (pos.0, pos.1 + last_width); - } + fn index_to_v2d(&self, line: &Line, index: usize) -> (usize, usize); /// Converts a visual 2d position into a grapheme index within a line. - fn v2d_to_index(&'a self, line: &'a Line, v2d: (usize, usize), rounding: (RoundingBehavior, RoundingBehavior)) -> usize { + fn v2d_to_index(&self, line: &Line, v2d: (usize, usize), rounding: (RoundingBehavior, RoundingBehavior)) -> usize; + + + fn index_to_horizontal_v2d(&self, buf: &Buffer, index: usize) -> usize { + let (line_i, col_i) = buf.index_to_line_col(index); + let line = buf.get_line(line_i); + return self.index_to_v2d(line, col_i).1; + } + + + /// Takes a grapheme index and a visual vertical offset, and returns the grapheme + /// index after that visual offset is applied. + fn index_offset_vertical_v2d(&self, buf: &Buffer, index: usize, offset: isize, rounding: (RoundingBehavior, RoundingBehavior)) -> usize { // TODO: handle rounding modes - let mut i = 0; + // TODO: do this with bidirectional line iterator + let (mut line_i, mut col_i) = buf.index_to_line_col(index); + let (mut y, x) = self.index_to_v2d(buf.get_line(line_i), col_i); + let mut new_y = y as isize + offset; - for (_, pos, _) in self.iter(line) { - if pos.0 > v2d.0 { - break; - } - else if pos.0 == v2d.0 && pos.1 >= v2d.1 { - break; - } + // First, find the right line while keeping track of the vertical offset + let mut line; + loop { + line = buf.get_line(line_i); + let (mut h, _) = self.dimensions(line); - i += 1; + if new_y >= 0 && new_y < h as isize { + y = new_y as usize; + break; + } + else { + if new_y < 0 { + // Check for off-the-end + if (line_i + 1) >= buf.line_count() { + return buf.grapheme_count(); + } + + line_i += 1; + new_y -= h as isize; + } + else if new_y > 0 { + // Check for off-the-end + if line_i == 0 { + return 0; + } + + line_i -= 1; + new_y -= h as isize; + } + else { + unreachable!(); + } + } } - return i; - } -} - - - -//================================================================ -// A simple implementation of LineFormatter, and LineFormatIter -// for testing purposes. -//================================================================ - -pub struct TestLineFormatIter<'a> { - grapheme_iter: LineGraphemeIter<'a>, - f: &'a TestLineFormatter, - pos: (usize, usize), -} - -impl<'a> Iterator for TestLineFormatIter<'a> { - type Item = (&'a str, (usize, usize), usize); - - fn next(&mut self) -> Option<(&'a str, (usize, usize), usize)> { - if let Some(g) = self.grapheme_iter.next() { - let pos = self.pos; - self.pos = (pos.0, pos.1 + 1); - return Some((g, pos, 1)); - } - else { - return None; - } - } -} - - -pub struct TestLineFormatter { - tab_width: u8 -} - -impl TestLineFormatter { - pub fn new() -> TestLineFormatter { - TestLineFormatter { - tab_width: 4, - } - } -} - -impl<'a> LineFormatter<'a> for TestLineFormatter { - type Iter = TestLineFormatIter<'a>; - - fn single_line_height(&self) -> usize { - 1 + // Next, convert the resulting coordinates back into buffer-wide + // coordinates. + let col_i = self.v2d_to_index(line, (y, x), rounding); + return buf.line_col_to_index((line_i, col_i)); } - fn iter(&'a self, line: &'a Line) -> TestLineFormatIter<'a> { - TestLineFormatIter { - grapheme_iter: line.grapheme_iter(), - f: self, - pos: (0, 0), - } + + fn index_set_horizontal_v2d(&self, buf: &Buffer, index: usize, horizontal: usize, rounding: RoundingBehavior) -> usize { + let (line_i, col_i) = buf.index_to_line_col(index); + let line = buf.get_line(line_i); + + let (v, h) = self.index_to_v2d(line, col_i); + let new_col_i = self.v2d_to_index(line, (v, horizontal), (RoundingBehavior::Floor, rounding)); + + return (index + new_col_i) - col_i; } + } -mod tests { - #![allow(unused_imports)] - use super::{TestLineFormatter, TestLineFormatIter}; - use buffer::line::Line; - - #[test] - fn simple_iterator() { - let line = Line::new_from_str("Hello!"); - let mut f = TestLineFormatter::new(); - let mut iter = f.iter(&line); - - let (a,_,_) = iter.next().unwrap(); - assert_eq!(a, "H"); - - let (a,_,_) = iter.next().unwrap(); - assert_eq!(a, "e"); - - let (a,_,_) = iter.next().unwrap(); - assert_eq!(a, "l"); - - let (a,_,_) = iter.next().unwrap(); - assert_eq!(a, "l"); - - let (a,_,_) = iter.next().unwrap(); - assert_eq!(a, "o"); - - let (a,_,_) = iter.next().unwrap(); - assert_eq!(a, "!"); - - let a = iter.next(); - assert_eq!(a, None); - } -} \ No newline at end of file +//==================================================================== +// UNIT TESTS +//==================================================================== + +//#[cfg(test)] +//mod tests { +// #![allow(unused_imports)] +// use buffer::line::{Line, LineGraphemeIter}; +// use super::LineFormatter; +// +// pub struct TestLineFormatIter<'a> { +// grapheme_iter: LineGraphemeIter<'a>, +// f: &'a TestLineFormatter, +// pos: (usize, usize), +// } +// +// impl<'a> Iterator for TestLineFormatIter<'a> { +// type Item = (&'a str, (usize, usize), usize); +// +// fn next(&mut self) -> Option<(&'a str, (usize, usize), usize)> { +// if let Some(g) = self.grapheme_iter.next() { +// let pos = self.pos; +// self.pos = (pos.0, pos.1 + 1); +// return Some((g, pos, 1)); +// } +// else { +// return None; +// } +// } +// } +// +// pub struct TestLineFormatter { +// tab_width: u8 +// } +// +// impl TestLineFormatter { +// pub fn new() -> TestLineFormatter { +// TestLineFormatter { +// tab_width: 4, +// } +// } +// } +// +// impl<'a> LineFormatter<'a, TestLineFormatIter<'a>> for TestLineFormatter { +// fn single_line_height(&self) -> usize { +// 1 +// } +// +// fn iter(&'a self, line: &'a Line) -> TestLineFormatIter<'a> { +// TestLineFormatIter { +// grapheme_iter: line.grapheme_iter(), +// f: self, +// pos: (0, 0), +// } +// } +// } +// +// +// #[test] +// fn simple_iterator() { +// let line = Line::new_from_str("Hello!"); +// let mut f = TestLineFormatter::new(); +// let mut iter = f.iter(&line); +// +// let (a,_,_) = iter.next().unwrap(); +// assert_eq!(a, "H"); +// +// let (a,_,_) = iter.next().unwrap(); +// assert_eq!(a, "e"); +// +// let (a,_,_) = iter.next().unwrap(); +// assert_eq!(a, "l"); +// +// let (a,_,_) = iter.next().unwrap(); +// assert_eq!(a, "l"); +// +// let (a,_,_) = iter.next().unwrap(); +// assert_eq!(a, "o"); +// +// let (a,_,_) = iter.next().unwrap(); +// assert_eq!(a, "!"); +// +// let a = iter.next(); +// assert_eq!(a, None); +// } +//}// \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 90a7a19..154c0ab 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,7 +10,7 @@ extern crate "rustc-serialize" as rustc_serialize; //extern crate freetype; //extern crate sdl2; -use std::path::Path; +use std::old_path::Path; use docopt::Docopt; use editor::Editor; use term_ui::TermUI; diff --git a/src/term_ui/formatter.rs b/src/term_ui/formatter.rs index 9a74c71..76093df 100644 --- a/src/term_ui/formatter.rs +++ b/src/term_ui/formatter.rs @@ -2,7 +2,7 @@ use std::cmp::max; use string_utils::{is_line_ending}; use buffer::line::{Line, LineGraphemeIter}; -use formatter::LineFormatter; +use formatter::{LineFormatter, RoundingBehavior}; //=================================================================== // LineFormatter implementation for terminals/consoles. @@ -21,17 +21,8 @@ impl ConsoleLineFormatter { wrap_width: 40, } } -} - - -impl<'a> LineFormatter<'a> for ConsoleLineFormatter { - type Iter = ConsoleLineFormatterVisIter<'a>; - - fn single_line_height(&self) -> usize { - return 1; - } - fn iter(&'a self, line: &'a Line) -> ConsoleLineFormatterVisIter<'a> { + pub fn iter<'a>(&'a self, line: &'a Line) -> ConsoleLineFormatterVisIter<'a> { ConsoleLineFormatterVisIter { grapheme_iter: line.grapheme_iter(), f: self, @@ -41,6 +32,66 @@ impl<'a> LineFormatter<'a> for ConsoleLineFormatter { } +impl LineFormatter for ConsoleLineFormatter { + fn single_line_height(&self) -> usize { + return 1; + } + + + fn dimensions(&self, line: &Line) -> (usize, usize) { + let mut dim: (usize, usize) = (0, 0); + + for (_, pos, width) in self.iter(line) { + dim = (max(dim.0, pos.0), max(dim.1, pos.1 + width)); + } + + dim.0 += self.single_line_height(); + + return dim; + } + + + fn index_to_v2d(&self, line: &Line, index: usize) -> (usize, usize) { + let mut pos = (0, 0); + let mut i = 0; + let mut last_width = 0; + + for (_, _pos, width) in self.iter(line) { + pos = _pos; + last_width = width; + i += 1; + + if i > index { + return pos; + } + } + + return (pos.0, pos.1 + last_width); + } + + + fn v2d_to_index(&self, line: &Line, v2d: (usize, usize), rounding: (RoundingBehavior, RoundingBehavior)) -> usize { + // TODO: handle rounding modes + let mut i = 0; + let mut pos = (0, 0); + + for (_, _pos, _) in self.iter(line) { + pos = _pos; + if pos.0 > v2d.0 { + break; + } + else if pos.0 == v2d.0 && pos.1 >= v2d.1 { + break; + } + + i += 1; + } + + return i; + } +} + + //=================================================================== // An iterator that iterates over the graphemes in a line in a // manner consistent with the ConsoleFormatter. diff --git a/src/term_ui/mod.rs b/src/term_ui/mod.rs index 0ed31a9..e5d9e1d 100644 --- a/src/term_ui/mod.rs +++ b/src/term_ui/mod.rs @@ -8,7 +8,7 @@ use std::char; use std::time::duration::Duration; use string_utils::{is_line_ending}; use buffer::line::{line_ending_to_str, LineEnding}; -use self::formatter::ConsoleLineFormatter; +use self::formatter::{ConsoleLineFormatter, ConsoleLineFormatterVisIter}; pub mod formatter; @@ -33,16 +33,16 @@ const K_CTRL_Y: u16 = 25; const K_CTRL_Z: u16 = 26; -pub struct TermUI<'a> { +pub struct TermUI { rb: rustbox::RustBox, - editor: Editor<'a, ConsoleLineFormatter>, + editor: Editor, width: usize, height: usize, } -impl<'a> TermUI<'a> { - pub fn new() -> TermUI<'a> { +impl TermUI { + pub fn new() -> TermUI { let rb = match rustbox::RustBox::init(&[Some(rustbox::InitOption::BufferStderr)]) { Ok(rbox) => rbox, Err(_) => panic!("Could not create Rustbox instance."), @@ -288,7 +288,7 @@ impl<'a> TermUI<'a> { // Jump to line! if confirm { - if let Some(n) = line.parse() { + if let Ok(n) = line.parse() { let n2: usize = n; // Weird work-around: the type of n wasn't being inferred if n2 > 0 { self.editor.jump_to_line(n2-1);