From a56ff9522116a3d46eb91e7fe9d90b430dea8966 Mon Sep 17 00:00:00 2001 From: Nathan Vegdahl Date: Sat, 20 Dec 2014 17:23:47 -0800 Subject: [PATCH] WIP: building a proper UI for editing a file. Some things don't quite work properly yet... --- src/buffer/mod.rs | 28 +++++++++++-- src/buffer/text_node.rs | 83 ++++++++++++++++++++++++++++++++++++++ src/editor.rs | 86 ++++++++++++++++++++++++++++++++++++++++ src/files.rs | 1 + src/main.rs | 88 ++++++++++++++++------------------------- src/term_ui.rs | 53 +++++++++++++++++++++++++ 6 files changed, 282 insertions(+), 57 deletions(-) create mode 100644 src/editor.rs create mode 100644 src/term_ui.rs diff --git a/src/buffer/mod.rs b/src/buffer/mod.rs index a5f479a..bfda080 100644 --- a/src/buffer/mod.rs +++ b/src/buffer/mod.rs @@ -1,6 +1,4 @@ - - - +#![allow(dead_code)] use std::fmt; use std; @@ -13,7 +11,7 @@ mod text_node; /// A text buffer pub struct TextBuffer { - pub root: TextNode + pub root: TextNode, } impl TextBuffer { @@ -27,6 +25,28 @@ impl TextBuffer { self.root.char_count } + pub fn pos_2d_to_1d(&self, pos: (uint, uint)) -> Option { + // TODO + return Option::None; + } + + pub fn pos_2d_to_closest_1d(&self, pos: (uint, uint)) -> uint { + match self.root.pos_2d_to_closest_1d(0, pos) { + text_node::IndexOrOffset::Index(i) => i, + _ => self.len() + } + } + + pub fn pos_2d_to_closest_2d(&self, pos: (uint, uint)) -> (uint, uint) { + // TODO + return (0, 0); + } + + pub fn pos_1d_to_2d(&self, pos: uint) -> Option<(uint, uint)> { + // TODO + return Option::None; + } + /// Insert 'text' at char position 'pos'. pub fn insert_text(&mut self, text: &str, pos: uint) { self.root.insert_text(text, pos); diff --git a/src/buffer/text_node.rs b/src/buffer/text_node.rs index fdfab2e..2f15798 100644 --- a/src/buffer/text_node.rs +++ b/src/buffer/text_node.rs @@ -10,6 +10,11 @@ use super::text_block::TextBlock; const MIN_LEAF_SIZE: uint = 64; const MAX_LEAF_SIZE: uint = MIN_LEAF_SIZE * 2; +pub enum IndexOrOffset { + Index(uint), + Offset(uint) +} + /// A text rope node, using TextBlocks for its underlying text /// storage. @@ -27,6 +32,7 @@ pub enum TextNodeData { } + impl TextNode { pub fn new() -> TextNode { TextNode { @@ -324,6 +330,83 @@ impl TextNode { self.update_height(); } } + + /// Find the closest 1d text position that represents the given + /// 2d position well. + pub fn pos_2d_to_closest_1d(&self, offset: uint, pos: (uint, uint)) -> IndexOrOffset { + match self.data { + TextNodeData::Leaf(ref tb) => { + let mut iter = tb.as_str().chars(); + let mut i = 0; + let mut line = 0; + let mut col = offset; + + for c in iter { + // Increment counters + if c == '\n' { + line += 1; + col = 0; + } + else { + col += 1; + } + i += 1; + + // Check if we've hit a relevant character + if line > pos.0 || (line == pos.0 && col > pos.1) { + break; + } + } + + // If we've reached the end of this text block but + // haven't reached the target position, return an + // offset of the amount of this line already consumed. + if pos.0 > line || (pos.0 == line && pos.1 > col) { + return IndexOrOffset::Offset(col); + } + + // Otherwise, we've found it! + return IndexOrOffset::Index(i); + }, + + TextNodeData::Branch(ref left, ref right) => { + // Left child + if pos.0 <= left.newline_count { + match left.pos_2d_to_closest_1d(offset, pos) { + IndexOrOffset::Index(il) => { + return IndexOrOffset::Index(il); + }, + + IndexOrOffset::Offset(il) => { + match right.pos_2d_to_closest_1d(il, (pos.0 - left.newline_count, pos.1)) { + IndexOrOffset::Index(ir) => { + return IndexOrOffset::Index(ir + left.char_count); + }, + + IndexOrOffset::Offset(ir) => { + return IndexOrOffset::Offset(ir); + } + } + } + } + } + // Right child + else { + match right.pos_2d_to_closest_1d(0, (pos.0 - left.newline_count, pos.1)) { + IndexOrOffset::Index(ir) => { + return IndexOrOffset::Index(ir); + }, + + IndexOrOffset::Offset(ir) => { + return IndexOrOffset::Offset(ir); + } + } + } + } + } + } + + } impl fmt::Show for TextNode { diff --git a/src/editor.rs b/src/editor.rs new file mode 100644 index 0000000..db59cfd --- /dev/null +++ b/src/editor.rs @@ -0,0 +1,86 @@ +#![allow(dead_code)] + +use buffer::TextBuffer; +use std::path::Path; +use files::{load_file_to_buffer, save_buffer_to_file}; + + +pub struct Editor { + pub buffer: TextBuffer, + pub file_path: Path, + pub dirty: bool, + + // The dimensions and position of the editor's view within the buffer + pub view_dim: (uint, uint), // (height, width) + pub view_pos: (uint, uint), // (line, col) + + // The editing cursor position + pub cursor: (uint, uint), // (line, col) +} + + +impl Editor { + /// Create a new blank editor + pub fn new() -> Editor { + Editor { + buffer: TextBuffer::new(), + file_path: Path::new(""), + dirty: false, + view_dim: (0, 0), + view_pos: (0, 0), + cursor: (0, 0), + } + } + + pub fn new_from_file(path: &Path) -> Editor { + let mut buf = load_file_to_buffer(path).unwrap(); + + Editor { + buffer: buf, + file_path: path.clone(), + dirty: false, + view_dim: (0, 0), + view_pos: (0, 0), + cursor: (0, 0), + } + } + + 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); + self.dirty = false; + } + } + + pub fn update_dim(&mut self, h: uint, w: uint) { + self.view_dim = (h, w); + } + + pub fn insert_text_at_cursor(&mut self, text: &str) { + let pos = self.buffer.pos_2d_to_closest_1d(self.cursor); + + self.buffer.insert_text(text, pos); + + self.dirty = true; + } + + pub fn cursor_left(&mut self) { + if self.cursor.1 > 0 { + self.cursor.1 -= 1; + } + } + + pub fn cursor_right(&mut self) { + self.cursor.1 += 1; + } + + pub fn cursor_up(&mut self) { + if self.cursor.0 > 0 { + self.cursor.0 -= 1; + } + } + + pub fn cursor_down(&mut self) { + self.cursor.0 += 1; + } +} diff --git a/src/files.rs b/src/files.rs index f2e096c..5201934 100644 --- a/src/files.rs +++ b/src/files.rs @@ -23,6 +23,7 @@ pub fn load_file_to_buffer(path: &Path) -> IoResult { } pub fn save_buffer_to_file(tb: &TextBuffer, path: &Path) -> IoResult<()> { + // TODO: make save atomic let mut iter = tb.root_iter(); let mut f = BufferedWriter::new(try!(File::create(path))); diff --git a/src/main.rs b/src/main.rs index 9b0ec63..236fb52 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,3 @@ -#![allow(dead_code)] - extern crate rustbox; extern crate docopt; extern crate serialize; @@ -7,12 +5,13 @@ extern crate serialize; use std::char; use std::path::Path; use docopt::Docopt; -use buffer::TextBuffer; -use rustbox::{Style,Color}; -use files::{load_file_to_buffer, save_buffer_to_file}; +use editor::Editor; +use term_ui::draw_editor; mod buffer; mod files; +mod editor; +mod term_ui; // Usage documentation string @@ -38,10 +37,10 @@ const K_ENTER: u16 = 13; const K_TAB: u16 = 9; const K_SPACE: u16 = 32; //const K_BACKSPACE: u16 = 127; -//const K_DOWN: u16 = 65516; -//const K_LEFT: u16 = 65515; -//const K_RIGHT: u16 = 65514; -//const K_UP: u16 = 65517; +const K_DOWN: u16 = 65516; +const K_LEFT: u16 = 65515; +const K_RIGHT: u16 = 65514; +const K_UP: u16 = 65517; const K_ESC: u16 = 27; const K_CTRL_Q: u16 = 17; const K_CTRL_S: u16 = 19; @@ -59,48 +58,19 @@ fn main() { let mut height = rustbox::height(); // Load file, if specified - let mut tb = if let Option::Some(s) = args.arg_file { - load_file_to_buffer(&Path::new(s.as_slice())).unwrap() + let mut editor = if let Option::Some(s) = args.arg_file { + Editor::new_from_file(&Path::new(s.as_slice())) } else { - TextBuffer::new() + Editor::new() }; rustbox::init(); loop { - // Draw the text buffer to screen + // Draw the editor to screen rustbox::clear(); - { - let mut tb_iter = tb.root_iter(); - let mut line: uint = 0; - let mut column: uint = 0; - - loop { - if let Option::Some(c) = tb_iter.next() { - if c == '\n' { - line += 1; - column = 0; - continue; - } - rustbox::print(column, line, Style::Normal, Color::White, Color::Black, c.to_string()); - column += 1; - } - else { - break; - } - - if line > height { - break; - } - - if column > width { - tb_iter.next_line(); - line += 1; - column = 0; - } - } - } + draw_editor(&editor, (0, 0), (height-1, width-1)); rustbox::present(); @@ -120,29 +90,41 @@ fn main() { }, K_CTRL_S => { - let _ = save_buffer_to_file(&tb, &Path::new("untitled.txt")); + editor.save_if_dirty(); + }, + + K_UP => { + editor.cursor_up(); + }, + + K_DOWN => { + editor.cursor_down(); + }, + + K_LEFT => { + editor.cursor_left(); + }, + + K_RIGHT => { + editor.cursor_right(); }, K_ENTER => { - let p = tb.len(); - tb.insert_text("\n", p); + editor.insert_text_at_cursor("\n"); }, K_SPACE => { - let p = tb.len(); - tb.insert_text(" ", p); + editor.insert_text_at_cursor(" "); }, K_TAB => { - let p = tb.len(); - tb.insert_text("\t", p); + editor.insert_text_at_cursor("\t"); }, // Character 0 => { if let Option::Some(c) = char::from_u32(character) { - let p = tb.len(); - tb.insert_text(c.to_string().as_slice(), p); + editor.insert_text_at_cursor(c.to_string().as_slice()); } }, @@ -172,5 +154,5 @@ fn main() { rustbox::shutdown(); - println!("{}", tb.root.tree_height); + //println!("{}", editor.buffer.root.tree_height); } diff --git a/src/term_ui.rs b/src/term_ui.rs new file mode 100644 index 0000000..f66062c --- /dev/null +++ b/src/term_ui.rs @@ -0,0 +1,53 @@ +use rustbox; +use rustbox::{Style,Color}; +use editor::Editor; + +pub fn draw_editor(editor: &Editor, c1: (uint, uint), c2: (uint, uint)) { + let mut tb_iter = editor.buffer.root_iter(); + let mut line: uint = 0; + let mut column: uint = 0; + let height = c2.0 - c1.0; + let width = c2.1 - c1.1; + + loop { + if let Option::Some(c) = tb_iter.next() { + if c == '\n' { + if editor.cursor.0 == line && editor.cursor.1 >= column && editor.cursor.1 <= width { + rustbox::print(editor.cursor.1, line, Style::Normal, Color::Black, Color::White, " ".to_string()); + } + + line += 1; + column = 0; + continue; + } + + if editor.cursor.0 == line && editor.cursor.1 == column { + rustbox::print(column, line, Style::Normal, Color::Black, Color::White, c.to_string()); + } + else { + rustbox::print(column, line, Style::Normal, Color::White, Color::Black, c.to_string()); + } + column += 1; + } + else { + break; + } + + if line > height { + break; + } + + if column > width { + tb_iter.next_line(); + line += 1; + column = 0; + } + } + + if editor.cursor.0 == line && editor.cursor.1 >= column && editor.cursor.1 <= width { + rustbox::print(editor.cursor.1, line, Style::Normal, Color::Black, Color::White, " ".to_string()); + } + else if editor.cursor.0 > line && editor.cursor.0 <= height && editor.cursor.1 <= width { + rustbox::print(editor.cursor.1, editor.cursor.0, Style::Normal, Color::Black, Color::White, " ".to_string()); + } +} \ No newline at end of file