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.)
This commit is contained in:
parent
8319033ae5
commit
3ae51f7f7e
|
@ -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<T: LineFormatter>(&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;
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<T: LineFormatter> {
|
||||
pub struct Editor<'a, T: LineFormatter<'a>> {
|
||||
pub buffer: Buffer,
|
||||
pub formatter: T,
|
||||
pub file_path: Path,
|
||||
|
@ -25,16 +24,16 @@ pub struct Editor<T: LineFormatter> {
|
|||
|
||||
// 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<T: LineFormatter> Editor<T> {
|
||||
impl<'a, T: LineFormatter<'a>> Editor<'a, T> {
|
||||
/// Create a new blank editor
|
||||
pub fn new(formatter: T) -> Editor<T> {
|
||||
pub fn new(formatter: T) -> Editor<'a, T> {
|
||||
Editor {
|
||||
buffer: Buffer::new(),
|
||||
formatter: formatter,
|
||||
|
@ -49,6 +48,7 @@ impl<T: LineFormatter> Editor<T> {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn new_from_file(formatter: T, path: &Path) -> Editor<T> {
|
||||
let buf = match Buffer::new_from_file(path) {
|
||||
Ok(b) => {b},
|
||||
|
@ -82,6 +82,7 @@ impl<T: LineFormatter> Editor<T> {
|
|||
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<T: LineFormatter> Editor<T> {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
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<T: LineFormatter> Editor<T> {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
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<T: LineFormatter> Editor<T> {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn update_dim(&mut self, h: usize, w: usize) {
|
||||
self.view_dim = (h, w);
|
||||
}
|
||||
|
@ -296,26 +300,16 @@ impl<T: LineFormatter> Editor<T> {
|
|||
// 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<T: LineFormatter> Editor<T> {
|
|||
self.move_view_to_cursor();
|
||||
}
|
||||
|
||||
|
||||
pub fn insert_tab_at_cursor(&mut self) {
|
||||
// TODO: update to new formatting code
|
||||
|
||||
|
@ -382,16 +377,19 @@ impl<T: LineFormatter> Editor<T> {
|
|||
//}
|
||||
}
|
||||
|
||||
|
||||
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<T: LineFormatter> Editor<T> {
|
|||
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<T: LineFormatter> Editor<T> {
|
|||
self.move_view_to_cursor();
|
||||
}
|
||||
|
||||
|
||||
pub fn remove_text_inside_cursor(&mut self) {
|
||||
self.cursors.make_consistent();
|
||||
|
||||
|
@ -496,6 +496,7 @@ impl<T: LineFormatter> Editor<T> {
|
|||
self.move_view_to_cursor();
|
||||
}
|
||||
|
||||
|
||||
pub fn cursor_to_beginning_of_buffer(&mut self) {
|
||||
self.cursors = CursorSet::new();
|
||||
|
||||
|
@ -506,6 +507,7 @@ impl<T: LineFormatter> Editor<T> {
|
|||
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<T: LineFormatter> Editor<T> {
|
|||
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<T: LineFormatter> Editor<T> {
|
|||
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<T: LineFormatter> Editor<T> {
|
|||
self.move_view_to_cursor();
|
||||
}
|
||||
|
||||
|
||||
pub fn cursor_up(&mut self, n: usize) {
|
||||
// TODO: update to new formatting code
|
||||
|
||||
|
@ -571,6 +576,7 @@ impl<T: LineFormatter> Editor<T> {
|
|||
//self.move_view_to_cursor();
|
||||
}
|
||||
|
||||
|
||||
pub fn cursor_down(&mut self, n: usize) {
|
||||
// TODO: update to new formatting code
|
||||
|
||||
|
@ -594,6 +600,7 @@ impl<T: LineFormatter> Editor<T> {
|
|||
//self.move_view_to_cursor();
|
||||
}
|
||||
|
||||
|
||||
pub fn page_up(&mut self) {
|
||||
// TODO: update to new formatting code
|
||||
|
||||
|
@ -614,6 +621,7 @@ impl<T: LineFormatter> Editor<T> {
|
|||
//self.move_view_to_cursor();
|
||||
}
|
||||
|
||||
|
||||
pub fn page_down(&mut self) {
|
||||
// TODO: update to new formatting code
|
||||
|
||||
|
@ -638,6 +646,7 @@ impl<T: LineFormatter> Editor<T> {
|
|||
//self.move_view_to_cursor();
|
||||
}
|
||||
|
||||
|
||||
pub fn jump_to_line(&mut self, n: usize) {
|
||||
// TODO: update to new formatting code
|
||||
|
||||
|
|
111
src/formatter.rs
Normal file
111
src/formatter.rs
Normal file
|
@ -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<Item=(&'a str, (usize, usize), (usize, usize))> + '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);
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
impl<'a> LineFormatter<'a> for ConsoleLineFormatter {
|
||||
type Iter = ConsoleLineFormatterVisIter<'a>;
|
||||
|
||||
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;
|
||||
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 {
|
||||
|
|
|
@ -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<ConsoleLineFormatter>,
|
||||
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."),
|
||||
|
|
Loading…
Reference in New Issue
Block a user