diff --git a/src/editor/cursor.rs b/src/editor/cursor.rs index 803b2fa..e91ce01 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 super::formatter::LineFormatter; +use 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, f: &T) { + 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; diff --git a/src/editor/formatter.rs b/src/editor/formatter.rs deleted file mode 100644 index b89c36d..0000000 --- a/src/editor/formatter.rs +++ /dev/null @@ -1,61 +0,0 @@ -use buffer::line::Line; -use std::cmp::min; - -#[derive(Copy, PartialEq)] -pub enum RoundingBehavior { - Round, - Floor, - Ceiling, -} - - -pub trait LineFormatter { - fn single_line_height(&self) -> usize; - - 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; -} - - - -//================================================================ -// A simple implementation of LineFormatter, for testing purposes -//================================================================ - -pub struct TestLineFormatter { - tab_width: u8 -} - -impl TestLineFormatter { - pub fn new() -> TestLineFormatter { - TestLineFormatter { - tab_width: 4, - } - } -} - -impl LineFormatter for TestLineFormatter { - fn single_line_height(&self) -> usize { - 1 - } - - fn dimensions(&self, line: &Line) -> (usize, usize) { - (1, line.grapheme_count()) - } - - fn index_to_v2d(&self, line: &Line, index: usize) -> (usize, usize) { - (0, min(line.grapheme_count(), index)) - } - - fn v2d_to_index(&self, line: &Line, v2d: (usize, usize), rounding: (RoundingBehavior, RoundingBehavior)) -> usize { - if v2d.0 > 0 { - line.grapheme_count() - } - else { - min(line.grapheme_count(), v2d.1) - } - } -} \ No newline at end of file diff --git a/src/editor/mod.rs b/src/editor/mod.rs index 8e4dbda..e9d1b59 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -2,19 +2,18 @@ use buffer::Buffer; use buffer::line::LineEnding; -use self::formatter::LineFormatter; -use self::formatter::RoundingBehavior::*; +use formatter::LineFormatter; +use formatter::RoundingBehavior::*; use std::path::Path; use std::cmp::{min, max}; use files::{save_buffer_to_file}; use string_utils::grapheme_count; use self::cursor::CursorSet; -pub mod formatter; mod cursor; -pub struct Editor { +pub struct Editor<'a, T: LineFormatter<'a>> { pub buffer: Buffer, pub formatter: T, pub file_path: Path, @@ -25,16 +24,16 @@ pub struct Editor { // The dimensions and position of the editor's view within the buffer pub view_dim: (usize, usize), // (height, width) - pub view_pos: (usize, usize), // (line, col) + pub view_pos: (usize, usize), // (grapheme index, visual horizontal offset) // The editing cursor position pub cursors: CursorSet, } -impl Editor { +impl<'a, T: LineFormatter<'a>> Editor<'a, T> { /// Create a new blank editor - pub fn new(formatter: T) -> Editor { + pub fn new(formatter: T) -> Editor<'a, T> { Editor { buffer: Buffer::new(), formatter: formatter, @@ -49,6 +48,7 @@ impl Editor { } } + pub fn new_from_file(formatter: T, path: &Path) -> Editor { let buf = match Buffer::new_from_file(path) { Ok(b) => {b}, @@ -82,6 +82,7 @@ impl Editor { return ed; } + pub fn save_if_dirty(&mut self) { if self.dirty && self.file_path != Path::new("") { let _ = save_buffer_to_file(&self.buffer, &self.file_path); @@ -89,6 +90,7 @@ impl Editor { } } + pub fn auto_detect_line_ending(&mut self) { let mut line_ending_histogram: [usize; 8] = [0, 0, 0, 0, 0, 0, 0, 0]; @@ -157,6 +159,7 @@ impl Editor { } } + pub fn auto_detect_indentation_style(&mut self) { let mut tab_blocks: usize = 0; let mut space_blocks: usize = 0; @@ -251,6 +254,7 @@ impl Editor { } } + pub fn update_dim(&mut self, h: usize, w: usize) { self.view_dim = (h, w); } @@ -296,26 +300,16 @@ impl Editor { // there are no cursors currently in view, and should jump to // the closest cursor. - // TODO: update to new formatting code - //let (v, h) = self.buffer.index_to_v2d(self.cursors[0].range.0); - // - //// Horizontal - //if h < self.view_pos.1 { - // self.view_pos.1 = h; - //} - //else if h >= (self.view_pos.1 + self.view_dim.1) { - // self.view_pos.1 = 1 + h - self.view_dim.1; - //} - // - //// Vertical - //if v < self.view_pos.0 { - // self.view_pos.0 = v; - //} - //else if v >= (self.view_pos.0 + self.view_dim.0) { - // self.view_pos.0 = 1 + v - self.view_dim.0; - //} + let gi = self.cursors[0].range.0; + let vho = self.cursors[0].vis_start; + + self.view_pos.0 = gi; + + // TODO: horizontal offset + //self.view_pos.1 = vho; } + pub fn insert_text_at_cursor(&mut self, text: &str) { self.cursors.make_consistent(); @@ -340,6 +334,7 @@ impl Editor { self.move_view_to_cursor(); } + pub fn insert_tab_at_cursor(&mut self) { // TODO: update to new formatting code @@ -382,16 +377,19 @@ impl Editor { //} } + pub fn backspace_at_cursor(&mut self) { self.remove_text_behind_cursor(1); } + pub fn insert_text_at_grapheme(&mut self, text: &str, pos: usize) { self.dirty = true; let buf_len = self.buffer.grapheme_count(); self.buffer.insert_text(text, if pos < buf_len {pos} else {buf_len}); } + pub fn remove_text_behind_cursor(&mut self, grapheme_count: usize) { self.cursors.make_consistent(); @@ -428,6 +426,7 @@ impl Editor { self.move_view_to_cursor(); } + pub fn remove_text_in_front_of_cursor(&mut self, grapheme_count: usize) { self.cursors.make_consistent(); @@ -463,6 +462,7 @@ impl Editor { self.move_view_to_cursor(); } + pub fn remove_text_inside_cursor(&mut self) { self.cursors.make_consistent(); @@ -496,6 +496,7 @@ impl Editor { self.move_view_to_cursor(); } + pub fn cursor_to_beginning_of_buffer(&mut self) { self.cursors = CursorSet::new(); @@ -506,6 +507,7 @@ impl Editor { self.move_view_to_cursor(); } + pub fn cursor_to_end_of_buffer(&mut self) { let end = self.buffer.grapheme_count(); @@ -517,6 +519,7 @@ impl Editor { self.move_view_to_cursor(); } + pub fn cursor_left(&mut self, n: usize) { for c in self.cursors.iter_mut() { if c.range.0 >= n { @@ -534,6 +537,7 @@ impl Editor { self.move_view_to_cursor(); } + pub fn cursor_right(&mut self, n: usize) { for c in self.cursors.iter_mut() { c.range.1 += n; @@ -550,6 +554,7 @@ impl Editor { self.move_view_to_cursor(); } + pub fn cursor_up(&mut self, n: usize) { // TODO: update to new formatting code @@ -571,6 +576,7 @@ impl Editor { //self.move_view_to_cursor(); } + pub fn cursor_down(&mut self, n: usize) { // TODO: update to new formatting code @@ -594,6 +600,7 @@ impl Editor { //self.move_view_to_cursor(); } + pub fn page_up(&mut self) { // TODO: update to new formatting code @@ -614,6 +621,7 @@ impl Editor { //self.move_view_to_cursor(); } + pub fn page_down(&mut self) { // TODO: update to new formatting code @@ -638,6 +646,7 @@ impl Editor { //self.move_view_to_cursor(); } + pub fn jump_to_line(&mut self, n: usize) { // TODO: update to new formatting code diff --git a/src/formatter.rs b/src/formatter.rs new file mode 100644 index 0000000..8b3da71 --- /dev/null +++ b/src/formatter.rs @@ -0,0 +1,111 @@ +use buffer::line::{Line, LineGraphemeIter}; +use std::cmp::min; + +#[derive(Copy, PartialEq)] +pub enum RoundingBehavior { + Round, + Floor, + Ceiling, +} + + +pub trait LineFormatter<'a> { + type Iter: Iterator + 'a; + + fn single_line_height(&self) -> usize; + + fn iter(&'a self, line: &'a Line) -> Self::Iter; +} + + + +//================================================================ +// 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, usize)); + + fn next(&mut self) -> Option<(&'a str, (usize, usize), (usize, usize))> { + if let Some(g) = self.grapheme_iter.next() { + let pos = self.pos; + self.pos = (pos.0, pos.1 + 1); + return Some((g, pos, (1, self.f.tab_width as usize))); + } + 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 + } + + fn iter(&'a self, line: &'a Line) -> TestLineFormatIter<'a> { + TestLineFormatIter { + grapheme_iter: line.grapheme_iter(), + f: self, + pos: (0, 0), + } + } +} + + + + +mod tests { + use super::{LineFormatter, 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 diff --git a/src/main.rs b/src/main.rs index d90a133..90a7a19 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,6 +20,7 @@ use term_ui::formatter::ConsoleLineFormatter; mod string_utils; mod buffer; +mod formatter; mod files; mod editor; mod term_ui; @@ -56,7 +57,7 @@ fn main() { let args: Args = Docopt::new(USAGE).and_then(|d| d.decode()).unwrap_or_else(|e| e.exit()); - // Initialize and start UI + //Initialize and start UI if args.flag_gui { // // Load file, if specified // let editor = if let Option::Some(s) = args.arg_file { diff --git a/src/term_ui/formatter.rs b/src/term_ui/formatter.rs index 87c8e13..d9f8ac2 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 editor::formatter::{LineFormatter, RoundingBehavior}; +use formatter::{LineFormatter, RoundingBehavior}; //=================================================================== // LineFormatter implementation for terminals/consoles. @@ -21,22 +21,17 @@ impl ConsoleLineFormatter { wrap_width: 40, } } +} - /// 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; +impl<'a> LineFormatter<'a> for ConsoleLineFormatter { + type Iter = ConsoleLineFormatterVisIter<'a>; + + fn single_line_height(&self) -> usize { + return 1; } - - - pub fn vis_grapheme_iter<'b>(&'b self, line: &'b Line) -> ConsoleLineFormatterVisIter<'b> { + + fn iter(&'a self, line: &'a Line) -> ConsoleLineFormatterVisIter<'a> { ConsoleLineFormatterVisIter { grapheme_iter: line.grapheme_iter(), f: self, @@ -46,63 +41,6 @@ impl ConsoleLineFormatter { } -impl<'a> 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.vis_grapheme_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.vis_grapheme_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; - - for (_, pos, _) in self.vis_grapheme_iter(line) { - 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. @@ -116,21 +54,21 @@ pub struct ConsoleLineFormatterVisIter<'a> { impl<'a> Iterator for ConsoleLineFormatterVisIter<'a> { - type Item = (&'a str, (usize, usize), usize); + type Item = (&'a str, (usize, usize), (usize, usize)); - fn next(&mut self) -> Option<(&'a str, (usize, usize), usize)> { + fn next(&mut self) -> Option<(&'a str, (usize, usize), (usize, usize))> { if let Some(g) = self.grapheme_iter.next() { let width = grapheme_vis_width_at_vis_pos(g, self.pos.1, self.f.tab_width as usize); if (self.pos.1 + width) > self.f.wrap_width { let pos = (self.pos.0 + self.f.single_line_height(), 0); self.pos = (self.pos.0 + self.f.single_line_height(), width); - return Some((g, pos, width)); + return Some((g, pos, (1, width))); } else { let pos = self.pos; self.pos = (self.pos.0, self.pos.1 + width); - return Some((g, pos, width)); + return Some((g, pos, (1, width))); } } else { diff --git a/src/term_ui/mod.rs b/src/term_ui/mod.rs index 5733792..004b124 100644 --- a/src/term_ui/mod.rs +++ b/src/term_ui/mod.rs @@ -3,7 +3,7 @@ use rustbox; use rustbox::Color; use editor::Editor; -use editor::formatter::{LineFormatter, RoundingBehavior}; +use formatter::{LineFormatter, RoundingBehavior}; use std::char; use std::time::duration::Duration; use string_utils::{is_line_ending}; @@ -33,16 +33,16 @@ const K_CTRL_Y: u16 = 25; const K_CTRL_Z: u16 = 26; -pub struct TermUI { +pub struct TermUI<'a> { rb: rustbox::RustBox, - editor: Editor, + editor: Editor<'a, ConsoleLineFormatter>, width: usize, height: usize, } -impl TermUI { - pub fn new() -> TermUI { +impl<'a> TermUI<'a> { + pub fn new() -> TermUI<'a> { let rb = match rustbox::RustBox::init(&[Some(rustbox::InitOption::BufferStderr)]) { Ok(rbox) => rbox, Err(_) => panic!("Could not create Rustbox instance."),