From 3ae51f7f7ee91802b8ce59a005922cd9021ab3e4 Mon Sep 17 00:00:00 2001 From: Nathan Vegdahl Date: Fri, 6 Feb 2015 23:58:19 -0800 Subject: [PATCH] Formatters are now primarily factories for iterators. This is a big shift in the formatter API's. It simplifies the responsibilities of the implementers so that pretty much all they have to do it implement an iterator. Everything else will be automatically derived from that. (Or, at least, that's the hope.) --- src/editor/cursor.rs | 4 +- src/editor/formatter.rs | 61 --------------------- src/editor/mod.rs | 59 ++++++++++++--------- src/formatter.rs | 111 +++++++++++++++++++++++++++++++++++++++ src/main.rs | 3 +- src/term_ui/formatter.rs | 88 +++++-------------------------- src/term_ui/mod.rs | 10 ++-- 7 files changed, 167 insertions(+), 169 deletions(-) delete mode 100644 src/editor/formatter.rs create mode 100644 src/formatter.rs 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."),