WOrking on getting cursor movement working again.

This commit is contained in:
Nathan Vegdahl 2015-02-07 19:46:08 -08:00
parent e9c06615f9
commit 26965417f3
8 changed files with 317 additions and 255 deletions

View File

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

View File

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

View File

@ -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<T: LineFormatter> {
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<T: LineFormatter> Editor<T> {
/// Create a new blank editor
pub fn new(formatter: T) -> Editor<'a, T> {
pub fn new(formatter: T) -> Editor<T> {
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));
//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();
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;
}
// 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));
//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();
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;
}
// Adjust view
self.move_view_to_cursor();
}

View File

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

View File

@ -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<Item=(&'a str, (usize, usize), usize)> + '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 {
// 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);
if new_y >= 0 && new_y < h as isize {
y = new_y as usize;
break;
}
else if pos.0 == v2d.0 && pos.1 >= v2d.1 {
break;
}
i += 1;
}
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;
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!();
}
}
}
pub struct TestLineFormatter {
tab_width: u8
// 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));
}
impl TestLineFormatter {
pub fn new() -> TestLineFormatter {
TestLineFormatter {
tab_width: 4,
}
}
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;
}
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 {
#![allow(unused_imports)]
use super::{TestLineFormatter, TestLineFormatIter};
use buffer::line::Line;
//====================================================================
// UNIT TESTS
//====================================================================
#[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);
}
}
//#[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);
// }
//}//

View File

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

View File

@ -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.

View File

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