WIP: building a proper UI for editing a file.
Some things don't quite work properly yet...
This commit is contained in:
parent
421b5288a4
commit
a56ff95221
|
@ -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<uint> {
|
||||
// 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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
86
src/editor.rs
Normal file
86
src/editor.rs
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -23,6 +23,7 @@ pub fn load_file_to_buffer(path: &Path) -> IoResult<TextBuffer> {
|
|||
}
|
||||
|
||||
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)));
|
||||
|
||||
|
|
88
src/main.rs
88
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);
|
||||
}
|
||||
|
|
53
src/term_ui.rs
Normal file
53
src/term_ui.rs
Normal file
|
@ -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());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user