From 45e6125bbcee53bce4a054ad47e5edb16afbd524 Mon Sep 17 00:00:00 2001 From: Nathan Vegdahl Date: Sat, 5 Mar 2016 02:40:01 -0800 Subject: [PATCH] Updated to work with more recent library versions. Also RustFmt. --- .gitignore | 2 +- Cargo.lock | 247 +++++++++++++ Cargo.toml | 11 +- src/buffer/mod.rs | 750 +++++++++++++++++++-------------------- src/buffer/undo_stack.rs | 20 +- src/editor/cursor.rs | 45 ++- src/editor/mod.rs | 441 ++++++++++++----------- src/formatter.rs | 142 ++++---- src/main.rs | 37 +- src/string_utils.rs | 148 ++++---- src/term_ui/formatter.rs | 511 +++++++++++++++----------- src/term_ui/mod.rs | 361 +++++++++++-------- src/utils.rs | 4 +- 13 files changed, 1582 insertions(+), 1137 deletions(-) create mode 100644 Cargo.lock diff --git a/.gitignore b/.gitignore index b56d5ef..159ad87 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ /target -/Cargo.lock +*.rs.bk .zedstate diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..85b8f3c --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,247 @@ +[root] +name = "Led" +version = "0.0.1" +dependencies = [ + "docopt 0.6.78 (registry+https://github.com/rust-lang/crates.io-index)", + "encoding 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)", + "ropey 0.3.0 (git+https://github.com/cessen/ropey.git)", + "rustbox 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-segmentation 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "aho-corasick" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bitflags" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "docopt" +version = "0.6.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "regex 0.1.55 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)", + "strsim 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "encoding" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "encoding-index-japanese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)", + "encoding-index-korean 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)", + "encoding-index-simpchinese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)", + "encoding-index-singlebyte 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)", + "encoding-index-tradchinese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "encoding-index-japanese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "encoding-index-korean" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "encoding-index-simpchinese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "encoding-index-singlebyte" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "encoding-index-tradchinese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "encoding_index_tests" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "gag" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "tempfile 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "kernel32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "libc" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "memchr" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex" +version = "0.1.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "aho-corasick 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", + "utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex-syntax" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "ropey" +version = "0.3.0" +source = "git+https://github.com/cessen/ropey.git#aa4dcf8d39d77f295126eed77d56f4b93a069b49" +dependencies = [ + "unicode-segmentation 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rustbox" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "gag 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "num 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)", + "termbox-sys 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rustc-serialize" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rustc_version" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "semver" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "strsim" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "tempfile" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "termbox-sys" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unicode-segmentation" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unicode-width" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "utf8-ranges" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + diff --git a/Cargo.toml b/Cargo.toml index df965e7..6b42f5a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,10 +18,9 @@ git = "https://github.com/cessen/ropey.git" #git = "https://github.com/PistonDevelopers/freetype-rs.git" [dependencies] -time = "0.1.30" rustc-serialize = "0.3.0" -unicode-segmentation = "0.1.*" -unicode-width = "0.1.*" -docopt = "0.6.*" -encoding = "*" -rustbox = "0.6.3" +unicode-segmentation = "0.1" +unicode-width = "0.1" +docopt = "0.6" +encoding = "0.2" +rustbox = "0.8" diff --git a/src/buffer/mod.rs b/src/buffer/mod.rs index f010a1b..28c5ac4 100644 --- a/src/buffer/mod.rs +++ b/src/buffer/mod.rs @@ -8,16 +8,16 @@ use std::io; use std::io::{BufReader, BufWriter, Read, Write}; use ropey::{Rope, RopeSlice, RopeGraphemeIter, RopeLineIter}; -use self::undo_stack::{UndoStack}; +use self::undo_stack::UndoStack; use self::undo_stack::Operation::*; use string_utils::grapheme_count; mod undo_stack; -//============================================================= +// ============================================================= // Buffer -//============================================================= +// ============================================================= /// A text buffer pub struct Buffer { @@ -36,8 +36,8 @@ impl Buffer { undo_stack: UndoStack::new(), } } - - + + pub fn new_from_str(s: &str) -> Buffer { Buffer { text: Rope::from_str(s), @@ -45,104 +45,104 @@ impl Buffer { undo_stack: UndoStack::new(), } } - - + + pub fn new_from_file(path: &Path) -> io::Result { let mut f = BufReader::new(try!(File::open(path))); let mut string = String::new(); try!(f.read_to_string(&mut string)); - + let buf = Buffer { text: Rope::from_str(&string[..]), file_path: Some(path.to_path_buf()), undo_stack: UndoStack::new(), }; - + return Ok(buf); } - - + + pub fn save_to_file(&self, path: &Path) -> io::Result<()> { let mut f = BufWriter::new(try!(File::create(path))); - + for c in self.text.chunk_iter() { let _ = f.write(c.as_bytes()); } - + return Ok(()); } - - - //------------------------------------------------------------------------ + + + // ------------------------------------------------------------------------ // Functions for getting information about the buffer. - //------------------------------------------------------------------------ - + // ------------------------------------------------------------------------ + pub fn char_count(&self) -> usize { self.text.char_count() } - - + + pub fn grapheme_count(&self) -> usize { self.text.grapheme_count() } - + pub fn line_count(&self) -> usize { self.text.line_ending_count() + 1 } - - - - //------------------------------------------------------------------------ + + + + // ------------------------------------------------------------------------ // Editing operations - //------------------------------------------------------------------------ - + // ------------------------------------------------------------------------ + /// Insert 'text' at grapheme position 'pos'. pub fn insert_text(&mut self, text: &str, pos: usize) { let cpos = self.text.grapheme_index_to_char_index(pos); self._insert_text(text, cpos); - + self.undo_stack.push(InsertText(text.to_string(), cpos)); } - + fn _insert_text(&mut self, text: &str, pos: usize) { self.text.insert_text_at_char_index(text, pos); } - + /// Remove the text before grapheme position 'pos' of length 'len'. pub fn remove_text_before(&mut self, pos: usize, len: usize) { if pos >= len { let cpos_a = self.text.grapheme_index_to_char_index(pos); let cpos_b = self.text.grapheme_index_to_char_index(pos - len); let removed_text = self.string_from_range(cpos_b, cpos_a); - + self._remove_text(cpos_b, cpos_a); - + // Push operation to the undo stack self.undo_stack.push(RemoveTextBefore(removed_text, cpos_b)); - } - else { - panic!("Buffer::remove_text_before(): attempt to remove text before beginning of buffer."); + } else { + panic!("Buffer::remove_text_before(): attempt to remove text before beginning of \ + buffer."); } } - + /// Remove the text after grapheme position 'pos' of length 'len'. pub fn remove_text_after(&mut self, pos: usize, len: usize) { let cpos_a = self.text.grapheme_index_to_char_index(pos); let cpos_b = self.text.grapheme_index_to_char_index(pos + len); - + let removed_text = self.string_from_range(cpos_a, cpos_b); - + self._remove_text(cpos_a, cpos_b); - + // Push operation to the undo stack self.undo_stack.push(RemoveTextAfter(removed_text, cpos_a)); } - + fn _remove_text(&mut self, pos_a: usize, pos_b: usize) { // Nothing to do if pos_a == pos_b { @@ -166,8 +166,8 @@ impl Buffer { self.text.remove_text_between_char_indices(pos_a, pos_b); } } - - + + /// Moves the text in [pos_a, pos_b) to begin at index pos_to. /// /// Note that pos_to is the desired index that the text will start at @@ -177,13 +177,13 @@ impl Buffer { let cpos_a = self.text.grapheme_index_to_char_index(pos_a); let cpos_b = self.text.grapheme_index_to_char_index(pos_b); let cpos_to = self.text.grapheme_index_to_char_index(pos_to); - + self._move_text(cpos_a, cpos_b, cpos_to); - + // Push operation to the undo stack self.undo_stack.push(MoveText(cpos_a, cpos_b, cpos_to)); } - + fn _move_text(&mut self, pos_a: usize, pos_b: usize, pos_to: usize) { // Nothing to do if pos_a == pos_b || pos_a == pos_to { @@ -214,8 +214,8 @@ impl Buffer { self._insert_text(&s[..], pos_to); } } - - + + /// Removes the lines in line indices [line_a, line_b). /// TODO: undo pub fn remove_lines(&mut self, line_a: usize, line_b: usize) { @@ -240,34 +240,31 @@ impl Buffer { else { let a = if line_a > 0 { self.text.line_index_to_char_index(line_a) - 1 - } - else { + } else { 0 }; - + let b = if line_b < self.line_count() { if line_a > 0 { self.text.line_index_to_char_index(line_b) - 1 - } - else { + } else { self.text.line_index_to_char_index(line_b) } - } - else { + } else { self.text.char_count() }; - + self.text.remove_text_between_char_indices(a, b); } } - - - - - //------------------------------------------------------------------------ + + + + + // ------------------------------------------------------------------------ // Undo/redo functionality - //------------------------------------------------------------------------ - + // ------------------------------------------------------------------------ + /// Undoes operations that were pushed to the undo stack, and returns a /// cursor position that the cursor should jump to, if any. pub fn undo(&mut self) -> Option { @@ -275,37 +272,37 @@ impl Buffer { match op { InsertText(ref s, p) => { let size = grapheme_count(&s[..]); - self._remove_text(p, p+size); + self._remove_text(p, p + size); return Some(p); - }, - + } + RemoveTextBefore(ref s, p) => { let size = grapheme_count(&s[..]); self._insert_text(&s[..], p); - return Some(p+size); - }, - + return Some(p + size); + } + RemoveTextAfter(ref s, p) => { self._insert_text(&s[..], p); return Some(p); - }, - + } + MoveText(pa, pb, pto) => { let size = pb - pa; self._move_text(pto, pto + size, pa); return Some(pa); - }, - + } + _ => { return None; - }, + } } } - + return None; } - - + + /// Redoes the last undone operation, and returns a cursor position that /// the cursor should jump to, if any. pub fn redo(&mut self) -> Option { @@ -314,35 +311,35 @@ impl Buffer { InsertText(ref s, p) => { let size = grapheme_count(&s[..]); self._insert_text(&s[..], p); - return Some(p+size); - }, - + return Some(p + size); + } + RemoveTextBefore(ref s, p) | RemoveTextAfter(ref s, p) => { let size = grapheme_count(&s[..]); - self._remove_text(p, p+size); + self._remove_text(p, p + size); return Some(p); - }, - + } + MoveText(pa, pb, pto) => { self._move_text(pa, pb, pto); return Some(pa); - }, - + } + _ => { return None; - }, + } } } - + return None; } - - - - //------------------------------------------------------------------------ + + + + // ------------------------------------------------------------------------ // Position conversions - //------------------------------------------------------------------------ - + // ------------------------------------------------------------------------ + /// Converts a grapheme index into a line number and grapheme-column /// number. /// @@ -352,22 +349,21 @@ impl Buffer { // Convert to char index let cpos = if pos < self.text.grapheme_count() { self.text.grapheme_index_to_char_index(pos) - } - else { + } else { self.text.char_count() }; - + let line = self.text.char_index_to_line_index(cpos); let line_pos = self.text.line_index_to_char_index(line); - + // Convert back from char index let gp = self.text.char_index_to_grapheme_index(cpos); let gline_pos = self.text.char_index_to_grapheme_index(line_pos); - + return (line, gp - gline_pos); } - - + + /// Converts a line number and grapheme-column number into a grapheme /// index. /// @@ -379,119 +375,112 @@ impl Buffer { if pos.0 <= self.text.line_ending_count() { let temp1 = self.text.line_index_to_char_index(pos.0); let l_begin_pos = self.text.char_index_to_grapheme_index(temp1); - + let l_end_pos = if pos.0 < self.text.line_ending_count() { let temp2 = self.text.line_index_to_char_index(pos.0 + 1); self.text.char_index_to_grapheme_index(temp2) - 1 - } - else { + } else { self.text.grapheme_count() }; - + return min(l_begin_pos + pos.1, l_end_pos); - } - else { + } else { return self.text.grapheme_count(); } } - - - //------------------------------------------------------------------------ + + + // ------------------------------------------------------------------------ // Text reading functions - //------------------------------------------------------------------------ - + // ------------------------------------------------------------------------ + pub fn get_grapheme<'a>(&'a self, index: usize) -> &'a str { if index >= self.grapheme_count() { panic!("Buffer::get_grapheme(): index past last grapheme."); - } - else { + } else { return self.text.grapheme_at_index(index); } } - - + + pub fn get_line<'a>(&'a self, index: usize) -> RopeSlice<'a> { if index >= self.line_count() { panic!("get_line(): index out of bounds."); } - + let a = self.text.line_index_to_char_index(index); - let b = if (index+1) < self.line_count() { - self.text.line_index_to_char_index(index+1) - } - else { + let b = if (index + 1) < self.line_count() { + self.text.line_index_to_char_index(index + 1) + } else { self.text.char_count() }; - + return self.text.slice(a, b); } - - + + /// Creates a String from the buffer text in grapheme range [pos_a, posb). fn string_from_range(&self, pos_a: usize, pos_b: usize) -> String { // Bounds checks if pos_b < pos_a { panic!("Buffer::string_from_range(): pos_a must be less than or equal to pos_b."); - } - else if pos_b > self.grapheme_count() { + } else if pos_b > self.grapheme_count() { panic!("Buffer::string_from_range(): specified range is past end of buffer text."); } - + let mut s = String::with_capacity(pos_b - pos_a); - + let mut i = 0; let i_end = pos_b - pos_a; - + for g in self.text.grapheme_iter_at_index(pos_a) { if i == i_end { break; } - + s.push_str(g); - + i += 1; } - + return s; } - - - - //------------------------------------------------------------------------ + + + + // ------------------------------------------------------------------------ // Iterator creators - //------------------------------------------------------------------------ - + // ------------------------------------------------------------------------ + /// Creates an iterator at the first character pub fn grapheme_iter<'a>(&'a self) -> RopeGraphemeIter<'a> { self.text.grapheme_iter() } - - + + /// Creates an iterator starting at the specified grapheme index. /// If the index is past the end of the text, then the iterator will /// return None on next(). pub fn grapheme_iter_at_index<'a>(&'a self, index: usize) -> RopeGraphemeIter<'a> { self.text.grapheme_iter_at_index(index) } - - + + pub fn line_iter<'a>(&'a self) -> RopeLineIter<'a> { self.text.line_iter() } - - + + pub fn line_iter_at_index<'a>(&'a self, index: usize) -> RopeLineIter<'a> { self.text.line_iter_at_index(index) } - - } -//================================================================ +// ================================================================ // TESTS -//================================================================ +// ================================================================ #[cfg(test)] mod tests { @@ -501,11 +490,11 @@ mod tests { #[test] fn insert_text() { let mut buf = Buffer::new(); - + buf.insert_text("Hello 世界!", 0); - + let mut iter = buf.grapheme_iter(); - + assert!(buf.grapheme_count() == 9); assert!(buf.line_count() == 1); assert!(Some("H") == iter.next()); @@ -519,16 +508,16 @@ mod tests { assert!(Some("!") == iter.next()); assert!(None == iter.next()); } - - + + #[test] fn insert_text_with_newlines() { let mut buf = Buffer::new(); - + buf.insert_text("Hello\n 世界\r\n!", 0); - + let mut iter = buf.grapheme_iter(); - + assert!(buf.grapheme_count() == 11); assert!(buf.line_count() == 3); assert!(Some("H") == iter.next()); @@ -544,17 +533,17 @@ mod tests { assert!(Some("!") == iter.next()); assert!(None == iter.next()); } - - + + #[test] fn insert_text_in_non_empty_buffer_1() { let mut buf = Buffer::new(); - + buf.insert_text("Hello\n 世界\r\n!", 0); buf.insert_text("Again ", 0); - + let mut iter = buf.grapheme_iter(); - + assert!(buf.grapheme_count() == 17); assert!(buf.line_count() == 3); assert!(Some("A") == iter.next()); @@ -576,17 +565,17 @@ mod tests { assert!(Some("!") == iter.next()); assert!(None == iter.next()); } - - + + #[test] fn insert_text_in_non_empty_buffer_2() { let mut buf = Buffer::new(); - + buf.insert_text("Hello\n 世界\r\n!", 0); buf.insert_text(" again", 5); - + let mut iter = buf.grapheme_iter(); - + assert!(buf.grapheme_count() == 17); assert!(buf.line_count() == 3); assert!(Some("H") == iter.next()); @@ -608,17 +597,17 @@ mod tests { assert!(Some("!") == iter.next()); assert!(None == iter.next()); } - - + + #[test] fn insert_text_in_non_empty_buffer_3() { let mut buf = Buffer::new(); - + buf.insert_text("Hello\n 世界\r\n!", 0); buf.insert_text("again", 6); - + let mut iter = buf.grapheme_iter(); - + assert!(buf.grapheme_count() == 16); assert!(buf.line_count() == 3); assert!(Some("H") == iter.next()); @@ -639,17 +628,17 @@ mod tests { assert!(Some("!") == iter.next()); assert!(None == iter.next()); } - - + + #[test] fn insert_text_in_non_empty_buffer_4() { let mut buf = Buffer::new(); - + buf.insert_text("Hello\n 世界\r\n!", 0); buf.insert_text("again", 11); - + let mut iter = buf.grapheme_iter(); - + assert!(buf.grapheme_count() == 16); assert!(buf.line_count() == 3); assert!(Some("H") == iter.next()); @@ -670,17 +659,17 @@ mod tests { assert!(Some("n") == iter.next()); assert!(None == iter.next()); } - - + + #[test] fn insert_text_in_non_empty_buffer_5() { let mut buf = Buffer::new(); - + buf.insert_text("Hello\n 世界\r\n!", 0); buf.insert_text("again", 2); - + let mut iter = buf.grapheme_iter(); - + assert!(buf.grapheme_count() == 16); assert!(buf.line_count() == 3); assert!(Some("H") == iter.next()); @@ -699,20 +688,20 @@ mod tests { assert!(Some("界") == iter.next()); assert!(Some("\r\n") == iter.next()); assert!(Some("!") == iter.next()); - + assert!(None == iter.next()); } - - + + #[test] fn insert_text_in_non_empty_buffer_6() { let mut buf = Buffer::new(); - + buf.insert_text("Hello\n 世界\r\n!", 0); buf.insert_text("again", 8); - + let mut iter = buf.grapheme_iter(); - + assert!(buf.grapheme_count() == 16); assert!(buf.line_count() == 3); assert!(Some("H") == iter.next()); @@ -731,20 +720,20 @@ mod tests { assert!(Some("界") == iter.next()); assert!(Some("\r\n") == iter.next()); assert!(Some("!") == iter.next()); - + assert!(None == iter.next()); } - - + + #[test] fn insert_text_in_non_empty_buffer_7() { let mut buf = Buffer::new(); - + buf.insert_text("Hello\n 世界\r\n!", 0); buf.insert_text("\nag\n\nain\n", 2); - + let mut iter = buf.grapheme_iter(); - + assert!(buf.grapheme_count() == 20); assert!(buf.line_count() == 7); assert!(Some("H") == iter.next()); @@ -767,23 +756,23 @@ mod tests { assert!(Some("界") == iter.next()); assert!(Some("\r\n") == iter.next()); assert!(Some("!") == iter.next()); - + assert!(None == iter.next()); } - - + + #[test] fn remove_text_1() { let mut buf = Buffer::new(); - + buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); assert!(buf.grapheme_count() == 29); assert!(buf.line_count() == 6); - + buf._remove_text(0, 3); - + let mut iter = buf.grapheme_iter(); - + assert!(buf.grapheme_count() == 26); assert!(buf.line_count() == 5); assert!(Some("t") == iter.next()); @@ -814,20 +803,20 @@ mod tests { assert!(Some("!") == iter.next()); assert!(None == iter.next()); } - - + + #[test] fn remove_text_2() { let mut buf = Buffer::new(); - + buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); assert!(buf.grapheme_count() == 29); assert!(buf.line_count() == 6); - + buf._remove_text(0, 12); - + let mut iter = buf.grapheme_iter(); - + assert!(buf.grapheme_count() == 17); assert!(buf.line_count() == 4); assert!(Some("p") == iter.next()); @@ -849,20 +838,20 @@ mod tests { assert!(Some("!") == iter.next()); assert!(None == iter.next()); } - - + + #[test] fn remove_text_3() { let mut buf = Buffer::new(); - + buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); assert!(buf.grapheme_count() == 29); assert!(buf.line_count() == 6); - + buf._remove_text(5, 17); - + let mut iter = buf.grapheme_iter(); - + assert!(buf.grapheme_count() == 17); assert!(buf.line_count() == 4); assert!(Some("H") == iter.next()); @@ -884,20 +873,20 @@ mod tests { assert!(Some("!") == iter.next()); assert!(None == iter.next()); } - - + + #[test] fn remove_text_4() { let mut buf = Buffer::new(); - + buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); assert!(buf.grapheme_count() == 29); assert!(buf.line_count() == 6); - + buf._remove_text(23, 29); - + let mut iter = buf.grapheme_iter(); - + assert!(buf.grapheme_count() == 23); assert!(buf.line_count() == 6); assert!(Some("H") == iter.next()); @@ -925,20 +914,20 @@ mod tests { assert!(Some("\n") == iter.next()); assert!(None == iter.next()); } - - + + #[test] fn remove_text_5() { let mut buf = Buffer::new(); - + buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); assert!(buf.grapheme_count() == 29); assert!(buf.line_count() == 6); - + buf._remove_text(17, 29); - + let mut iter = buf.grapheme_iter(); - + assert!(buf.grapheme_count() == 17); assert!(buf.line_count() == 4); assert!(Some("H") == iter.next()); @@ -960,20 +949,20 @@ mod tests { assert!(Some("o") == iter.next()); assert!(None == iter.next()); } - - + + #[test] fn remove_text_6() { let mut buf = Buffer::new(); - + buf.insert_text("Hello\nworld!", 0); assert!(buf.grapheme_count() == 12); assert!(buf.line_count() == 2); - + buf._remove_text(3, 12); - + let mut iter = buf.grapheme_iter(); - + assert!(buf.grapheme_count() == 3); assert!(buf.line_count() == 1); assert!(Some("H") == iter.next()); @@ -981,20 +970,20 @@ mod tests { assert!(Some("l") == iter.next()); assert!(None == iter.next()); } - - + + #[test] fn remove_text_7() { let mut buf = Buffer::new(); - + buf.insert_text("Hi\nthere\nworld!", 0); assert!(buf.grapheme_count() == 15); assert!(buf.line_count() == 3); - + buf._remove_text(5, 15); - + let mut iter = buf.grapheme_iter(); - + assert!(buf.grapheme_count() == 5); assert!(buf.line_count() == 2); assert!(Some("H") == iter.next()); @@ -1004,20 +993,20 @@ mod tests { assert!(Some("h") == iter.next()); assert!(None == iter.next()); } - - + + #[test] fn remove_text_8() { let mut buf = Buffer::new(); - + buf.insert_text("Hello\nworld!", 0); assert!(buf.grapheme_count() == 12); assert!(buf.line_count() == 2); - + buf._remove_text(3, 11); - + let mut iter = buf.grapheme_iter(); - + assert!(buf.grapheme_count() == 4); assert!(buf.line_count() == 1); assert!(Some("H") == iter.next()); @@ -1026,20 +1015,20 @@ mod tests { assert!(Some("!") == iter.next()); assert!(None == iter.next()); } - - + + #[test] fn remove_text_9() { let mut buf = Buffer::new(); - + buf.insert_text("Hello\nworld!", 0); assert!(buf.grapheme_count() == 12); assert!(buf.line_count() == 2); - + buf._remove_text(8, 12); - + let mut iter = buf.grapheme_iter(); - + assert!(buf.grapheme_count() == 8); assert!(buf.line_count() == 2); assert!(Some("H") == iter.next()); @@ -1052,20 +1041,20 @@ mod tests { assert!(Some("o") == iter.next()); assert!(None == iter.next()); } - - + + #[test] fn remove_text_10() { let mut buf = Buffer::new(); - + buf.insert_text("12\n34\n56\n78", 0); assert!(buf.grapheme_count() == 11); assert!(buf.line_count() == 4); - + buf._remove_text(4, 11); - + let mut iter = buf.grapheme_iter(); - + assert!(buf.grapheme_count() == 4); assert!(buf.line_count() == 2); assert!(Some("1") == iter.next()); @@ -1074,20 +1063,20 @@ mod tests { assert!(Some("3") == iter.next()); assert!(None == iter.next()); } - - + + #[test] fn remove_text_11() { let mut buf = Buffer::new(); - + buf.insert_text("1234567890", 0); assert!(buf.grapheme_count() == 10); assert!(buf.line_count() == 1); - + buf._remove_text(9, 10); - + let mut iter = buf.grapheme_iter(); - + assert!(buf.grapheme_count() == 9); assert!(buf.line_count() == 1); assert!(Some("1") == iter.next()); @@ -1101,18 +1090,18 @@ mod tests { assert!(Some("9") == iter.next()); assert!(None == iter.next()); } - - + + #[test] fn move_text_1() { let mut buf = Buffer::new(); - + buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); - + buf.move_text(0, 3, 2); - + let mut iter = buf.grapheme_iter(); - + assert!(buf.grapheme_count() == 29); assert!(buf.line_count() == 6); assert!(Some("t") == iter.next()); @@ -1146,18 +1135,18 @@ mod tests { assert!(Some("!") == iter.next()); assert!(None == iter.next()); } - - + + #[test] fn move_text_2() { let mut buf = Buffer::new(); - + buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); - + buf.move_text(3, 8, 6); - + let mut iter = buf.grapheme_iter(); - + assert!(buf.grapheme_count() == 29); assert!(buf.line_count() == 6); assert!(Some("H") == iter.next()); @@ -1191,18 +1180,18 @@ mod tests { assert!(Some("!") == iter.next()); assert!(None == iter.next()); } - - + + #[test] fn move_text_3() { let mut buf = Buffer::new(); - + buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); - + buf.move_text(12, 17, 6); - + let mut iter = buf.grapheme_iter(); - + assert!(buf.grapheme_count() == 29); assert!(buf.line_count() == 6); assert!(Some("H") == iter.next()); @@ -1236,18 +1225,18 @@ mod tests { assert!(Some("!") == iter.next()); assert!(None == iter.next()); } - - + + #[test] fn move_text_4() { let mut buf = Buffer::new(); - + buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); - + buf.move_text(23, 29, 20); - + let mut iter = buf.grapheme_iter(); - + assert!(buf.grapheme_count() == 29); assert!(buf.line_count() == 6); assert!(Some("H") == iter.next()); @@ -1281,18 +1270,18 @@ mod tests { assert!(Some("\n") == iter.next()); assert!(None == iter.next()); } - - + + #[test] fn move_text_5() { let mut buf = Buffer::new(); - + buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); - + buf.move_text(0, 29, 0); - + let mut iter = buf.grapheme_iter(); - + assert!(buf.grapheme_count() == 29); assert!(buf.line_count() == 6); assert!(Some("H") == iter.next()); @@ -1326,20 +1315,20 @@ mod tests { assert!(Some("!") == iter.next()); assert!(None == iter.next()); } - - + + #[test] fn remove_lines_1() { let mut buf = Buffer::new(); - + buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); assert!(buf.grapheme_count() == 29); assert!(buf.line_count() == 6); - + buf.remove_lines(0, 3); - + let mut iter = buf.grapheme_iter(); - + assert!(buf.grapheme_count() == 13); assert!(buf.line_count() == 3); assert!(Some("o") == iter.next()); @@ -1357,20 +1346,20 @@ mod tests { assert!(Some("!") == iter.next()); assert!(None == iter.next()); } - - + + #[test] fn remove_lines_2() { let mut buf = Buffer::new(); - + buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); assert!(buf.grapheme_count() == 29); assert!(buf.line_count() == 6); - + buf.remove_lines(1, 4); - + let mut iter = buf.grapheme_iter(); - + assert!(buf.grapheme_count() == 13); assert!(buf.line_count() == 3); assert!(Some("H") == iter.next()); @@ -1388,20 +1377,20 @@ mod tests { assert!(Some("!") == iter.next()); assert!(None == iter.next()); } - - + + #[test] fn remove_lines_3() { let mut buf = Buffer::new(); - + buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); assert_eq!(buf.grapheme_count(), 29); assert_eq!(buf.line_count(), 6); - + buf.remove_lines(3, 6); - + let mut iter = buf.grapheme_iter(); - + assert_eq!(buf.grapheme_count(), 15); assert_eq!(buf.line_count(), 3); assert!(Some("H") == iter.next()); @@ -1421,20 +1410,20 @@ mod tests { assert!(Some("e") == iter.next()); assert!(None == iter.next()); } - - + + #[test] fn remove_lines_4() { let mut buf = Buffer::new(); - + buf.insert_text("Hi\nthere\npeople\nof\nthe\n", 0); assert_eq!(buf.grapheme_count(), 23); assert_eq!(buf.line_count(), 6); - + buf.remove_lines(3, 6); - + let mut iter = buf.grapheme_iter(); - + assert_eq!(buf.grapheme_count(), 15); assert_eq!(buf.line_count(), 3); assert!(Some("H") == iter.next()); @@ -1454,158 +1443,158 @@ mod tests { assert!(Some("e") == iter.next()); assert!(None == iter.next()); } - - + + #[test] fn remove_lines_5() { let mut buf = Buffer::new(); - + buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); assert_eq!(buf.grapheme_count(), 29); assert_eq!(buf.line_count(), 6); - + buf.remove_lines(0, 6); - + let mut iter = buf.grapheme_iter(); - + assert_eq!(buf.grapheme_count(), 0); assert_eq!(buf.line_count(), 1); assert!(None == iter.next()); } - - + + #[test] fn remove_lines_6() { let mut buf = Buffer::new(); - + buf.insert_text("Hi\nthere\npeople\nof\nthe\n", 0); assert_eq!(buf.grapheme_count(), 23); assert_eq!(buf.line_count(), 6); - + buf.remove_lines(0, 6); - + let mut iter = buf.grapheme_iter(); - + assert_eq!(buf.grapheme_count(), 0); assert_eq!(buf.line_count(), 1); assert!(None == iter.next()); } - - + + #[test] fn line_col_to_index_1() { let mut buf = Buffer::new(); buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); - + let pos = buf.line_col_to_index((2, 3)); - + assert!(pos == 12); } - - + + #[test] fn line_col_to_index_2() { let mut buf = Buffer::new(); buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); - + let pos = buf.line_col_to_index((2, 10)); - + assert!(pos == 15); } - + #[test] fn line_col_to_index_3() { let mut buf = Buffer::new(); buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); - + let pos = buf.line_col_to_index((10, 2)); - + assert!(pos == 29); } - - + + #[test] fn line_col_to_index_4() { let mut buf = Buffer::new(); buf.insert_text("Hello\nworld!\n", 0); - - assert_eq!(buf.line_col_to_index((0,0)), 0); - assert_eq!(buf.line_col_to_index((0,5)), 5); - assert_eq!(buf.line_col_to_index((0,6)), 5); - assert_eq!(buf.line_col_to_index((1,0)), 6); - assert_eq!(buf.line_col_to_index((1,6)), 12); - assert_eq!(buf.line_col_to_index((1,7)), 12); + assert_eq!(buf.line_col_to_index((0, 0)), 0); + assert_eq!(buf.line_col_to_index((0, 5)), 5); + assert_eq!(buf.line_col_to_index((0, 6)), 5); - assert_eq!(buf.line_col_to_index((2,0)), 13); - assert_eq!(buf.line_col_to_index((2,1)), 13); + assert_eq!(buf.line_col_to_index((1, 0)), 6); + assert_eq!(buf.line_col_to_index((1, 6)), 12); + assert_eq!(buf.line_col_to_index((1, 7)), 12); + + assert_eq!(buf.line_col_to_index((2, 0)), 13); + assert_eq!(buf.line_col_to_index((2, 1)), 13); } - - + + #[test] fn index_to_line_col_1() { let mut buf = Buffer::new(); buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); - + let pos = buf.index_to_line_col(5); - + assert!(pos == (1, 2)); } - - + + #[test] fn index_to_line_col_2() { let mut buf = Buffer::new(); buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); - + let pos = buf.index_to_line_col(50); - + assert!(pos == (5, 6)); } - + #[test] fn index_to_line_col_3() { let mut buf = Buffer::new(); buf.insert_text("Hello\nworld!\n", 0); - - assert_eq!(buf.index_to_line_col(0), (0,0)); - assert_eq!(buf.index_to_line_col(5), (0,5)); - assert_eq!(buf.index_to_line_col(6), (1,0)); - assert_eq!(buf.index_to_line_col(12), (1,6)); - assert_eq!(buf.index_to_line_col(13), (2,0)); - assert_eq!(buf.index_to_line_col(14), (2,0)); + + assert_eq!(buf.index_to_line_col(0), (0, 0)); + assert_eq!(buf.index_to_line_col(5), (0, 5)); + assert_eq!(buf.index_to_line_col(6), (1, 0)); + assert_eq!(buf.index_to_line_col(12), (1, 6)); + assert_eq!(buf.index_to_line_col(13), (2, 0)); + assert_eq!(buf.index_to_line_col(14), (2, 0)); } - - + + #[test] fn string_from_range_1() { let mut buf = Buffer::new(); buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); - + let s = buf.string_from_range(1, 12); - + assert!(&s[..] == "i\nthere\npeo"); } - - + + #[test] fn string_from_range_2() { let mut buf = Buffer::new(); buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); - + let s = buf.string_from_range(0, 29); - + assert!(&s[..] == "Hi\nthere\npeople\nof\nthe\nworld!"); } - - + + #[test] fn grapheme_iter_at_index_1() { let mut buf = Buffer::new(); buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); - + let mut iter = buf.grapheme_iter_at_index(16); - + assert!(Some("o") == iter.next()); assert!(Some("f") == iter.next()); assert!(Some("\n") == iter.next()); @@ -1621,18 +1610,17 @@ mod tests { assert!(Some("!") == iter.next()); assert!(None == iter.next()); } - - + + #[test] fn grapheme_iter_at_index_2() { let mut buf = Buffer::new(); buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); - + let mut iter = buf.grapheme_iter_at_index(29); - + assert!(None == iter.next()); } - - -} + +} diff --git a/src/buffer/undo_stack.rs b/src/buffer/undo_stack.rs index f00793a..474f8a9 100644 --- a/src/buffer/undo_stack.rs +++ b/src/buffer/undo_stack.rs @@ -25,32 +25,30 @@ impl UndoStack { stack_b: LinkedList::new(), } } - - + + pub fn push(&mut self, op: Operation) { self.stack_a.push_back(op); self.stack_b.clear(); } - - + + pub fn prev(&mut self) -> Option { if let Some(op) = self.stack_a.pop_back() { self.stack_b.push_back(op.clone()); return Some(op); - } - else { + } else { return None; } } - - + + pub fn next(&mut self) -> Option { if let Some(op) = self.stack_b.pop_back() { self.stack_a.push_back(op.clone()); return Some(op); - } - else { + } else { return None; } } -} \ No newline at end of file +} diff --git a/src/editor/cursor.rs b/src/editor/cursor.rs index 466db65..314b2d4 100644 --- a/src/editor/cursor.rs +++ b/src/editor/cursor.rs @@ -15,8 +15,8 @@ use formatter::LineFormatter; /// doesn't affect editing operations at all, but is used for cursor movement. #[derive(Copy, Clone)] pub struct Cursor { - pub range: (usize, usize), // start, end - pub vis_start: usize, // start + pub range: (usize, usize), // start, end + pub vis_start: usize, // start } impl Cursor { @@ -26,7 +26,7 @@ impl Cursor { vis_start: 0, } } - + pub fn update_vis_start(&mut self, buf: &Buffer, f: &T) { self.vis_start = f.index_to_horizontal_v2d(buf, self.range.0); } @@ -36,56 +36,51 @@ impl Cursor { /// A collection of cursors, managed to always be in a consistent /// state for multi-cursor editing. pub struct CursorSet { - cursors: Vec + cursors: Vec, } impl CursorSet { pub fn new() -> CursorSet { - CursorSet { - cursors: vec!(Cursor::new()), - } + CursorSet { cursors: vec![Cursor::new()] } } - + pub fn add_cursor(&mut self, cursor: Cursor) { self.cursors.push(cursor); self.make_consistent(); } - + pub fn truncate(&mut self, len: usize) { self.cursors.truncate(len); } - + pub fn iter<'a>(&'a self) -> Iter<'a, Cursor> { (&self.cursors[..]).iter() } - + pub fn iter_mut<'a>(&'a mut self) -> IterMut<'a, Cursor> { (&mut self.cursors[..]).iter_mut() } - + pub fn make_consistent(&mut self) { // First, sort the cursors by starting position self.cursors.sort_by(|a, b| { if a.range.0 < b.range.0 { Ordering::Less - } - else if a.range.0 > b.range.0 { + } else if a.range.0 > b.range.0 { Ordering::Greater - } - else { + } else { Ordering::Equal } }); - + // Next, merge overlapping cursors let mut i = 0; - while i < (self.cursors.len()-1) { - if self.cursors[i].range.1 >= self.cursors[i+1].range.0 { - self.cursors[i].range.1 = self.cursors[i+1].range.1; - self.cursors.remove(i+1); - } - else { + while i < (self.cursors.len() - 1) { + if self.cursors[i].range.1 >= self.cursors[i + 1].range.0 { + self.cursors[i].range.1 = self.cursors[i + 1].range.1; + self.cursors.remove(i + 1); + } else { i += 1; } } @@ -95,7 +90,7 @@ impl CursorSet { impl Index for CursorSet { type Output = Cursor; - + fn index<'a>(&'a self, _index: usize) -> &'a Cursor { &(self.cursors[_index]) } @@ -106,4 +101,4 @@ impl IndexMut for CursorSet { fn index_mut<'a>(&'a mut self, _index: usize) -> &'a mut Cursor { &mut (self.cursors[_index]) } -} \ No newline at end of file +} diff --git a/src/editor/mod.rs b/src/editor/mod.rs index 76256f3..284dab5 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -20,17 +20,17 @@ pub struct Editor { pub soft_tabs: bool, pub soft_tab_width: u8, pub dirty: bool, - + // The dimensions of the total editor in screen space, including the // header, gutter, etc. pub editor_dim: (usize, usize), - + // The dimensions and position of just the text view portion of the editor - pub view_dim: (usize, usize), // (height, width) - pub view_pos: (usize, usize), // (grapheme index, visual horizontal offset) - + pub view_dim: (usize, usize), // (height, width) + pub view_pos: (usize, usize), // (grapheme index, visual horizontal offset) + // The editing cursor position - pub cursors: CursorSet, + pub cursors: CursorSet, } @@ -51,15 +51,15 @@ impl Editor { cursors: CursorSet::new(), } } - - + + pub fn new_from_file(formatter: T, path: &Path) -> Editor { let buf = match Buffer::new_from_file(path) { - Ok(b) => {b}, + Ok(b) => b, // TODO: handle un-openable file better _ => panic!("Could not open file!"), }; - + let mut ed = Editor { buffer: buf, formatter: formatter, @@ -73,32 +73,32 @@ impl Editor { view_pos: (0, 0), cursors: CursorSet::new(), }; - + // For multiple-cursor testing - //let mut cur = Cursor::new(); - //cur.range.0 = 30; - //cur.range.1 = 30; - //cur.update_vis_start(&(ed.buffer), &(ed.formatter)); - //ed.cursors.add_cursor(cur); - + // let mut cur = Cursor::new(); + // cur.range.0 = 30; + // cur.range.1 = 30; + // cur.update_vis_start(&(ed.buffer), &(ed.formatter)); + // ed.cursors.add_cursor(cur); + ed.auto_detect_line_ending(); ed.auto_detect_indentation_style(); - + return ed; } - - + + pub fn save_if_dirty(&mut self) { if self.dirty && self.file_path != PathBuf::new() { let _ = self.buffer.save_to_file(&self.file_path); self.dirty = false; } } - - + + pub fn auto_detect_line_ending(&mut self) { let mut line_ending_histogram: [usize; 8] = [0, 0, 0, 0, 0, 0, 0, 0]; - + // Collect statistics let mut line_i: usize = 0; for line in self.buffer.line_iter() { @@ -106,48 +106,46 @@ impl Editor { let ending = if line.grapheme_count() > 0 { let g = line.grapheme_at_index(line.grapheme_count() - 1); str_to_line_ending(g) - } - else { + } else { LineEnding::None }; - + // Record which line ending it is match ending { - LineEnding::None => { - }, + LineEnding::None => {} LineEnding::CRLF => { line_ending_histogram[0] += 1; - }, + } LineEnding::LF => { line_ending_histogram[1] += 1; - }, + } LineEnding::VT => { line_ending_histogram[2] += 1; - }, + } LineEnding::FF => { line_ending_histogram[3] += 1; - }, + } LineEnding::CR => { line_ending_histogram[4] += 1; - }, + } LineEnding::NEL => { line_ending_histogram[5] += 1; - }, + } LineEnding::LS => { line_ending_histogram[6] += 1; - }, + } LineEnding::PS => { line_ending_histogram[7] += 1; - }, + } } - + // Stop after 100 lines line_i += 1; if line_i > 100 { break; } } - + // Analyze stats and make a determination let mut lei = 0; let mut le_count = 0; @@ -157,7 +155,7 @@ impl Editor { le_count = line_ending_histogram[i]; } } - + if le_count > 0 { self.line_ending_type = match lei { 0 => LineEnding::CRLF, @@ -168,20 +166,20 @@ impl Editor { 5 => LineEnding::NEL, 6 => LineEnding::LS, 7 => LineEnding::PS, - + _ => LineEnding::LF, }; } } - - + + pub fn auto_detect_indentation_style(&mut self) { let mut tab_blocks: usize = 0; let mut space_blocks: usize = 0; let mut space_histogram: [usize; 9] = [0, 0, 0, 0, 0, 0, 0, 0, 0]; - + let mut last_indent = (false, 0usize); // (was_tabs, indent_count) - + // Collect statistics let mut line_i: usize = 0; for line in self.buffer.line_iter() { @@ -193,64 +191,61 @@ impl Editor { for g in g_iter { if g == "\t" { count += 1; - } - else { + } else { break; } } - + // Update stats if last_indent.0 && last_indent.1 < count { tab_blocks += 1; } - + // Store last line info last_indent = (true, count); - }, - + } + Some(" ") => { // Count leading spaces let mut count = 1; for g in g_iter { if g == " " { count += 1; - } - else { + } else { break; } } - + // Update stats if !last_indent.0 && last_indent.1 < count { space_blocks += 1; let amount = count - last_indent.1; if amount < 9 { space_histogram[amount] += 1; - } - else { + } else { space_histogram[8] += 1; } } - + // Store last line info last_indent = (false, count); - }, - - _ => {}, + } + + _ => {} } - + // Stop after 1000 lines line_i += 1; if line_i > 1000 { break; } } - + // Analyze stats and make a determination if space_blocks == 0 && tab_blocks == 0 { return; } - + if space_blocks > (tab_blocks * 2) { let mut width = 0; let mut width_count = 0; @@ -260,22 +255,21 @@ impl Editor { width_count = space_histogram[i]; } } - + self.soft_tabs = true; self.soft_tab_width = width as u8; - } - else { + } else { self.soft_tabs = false; } } - - + + pub fn update_dim(&mut self, h: usize, w: usize) { self.editor_dim = (h, w); self.update_view_dim(); } - - + + pub fn update_view_dim(&mut self) { // TODO: generalize for non-terminal UI. Maybe this isn't where it // belongs, in fact. But for now, this is the easiest place to put @@ -283,10 +277,11 @@ impl Editor { let line_count_digits = digit_count(self.buffer.line_count() as u32, 10) as usize; // Minus 1 vertically for the header, minus one more than the digits in // the line count for the gutter. - self.view_dim = (self.editor_dim.0 - 1, self.editor_dim.1 - line_count_digits - 1); + self.view_dim = (self.editor_dim.0 - 1, + self.editor_dim.1 - line_count_digits - 1); } - - + + pub fn undo(&mut self) { // TODO: handle multiple cursors properly if let Some(pos) = self.buffer.undo() { @@ -294,16 +289,16 @@ impl Editor { self.cursors[0].range.0 = pos; self.cursors[0].range.1 = pos; self.cursors[0].update_vis_start(&(self.buffer), &(self.formatter)); - + self.move_view_to_cursor(); - + self.dirty = true; - + self.cursors.make_consistent(); } } - - + + pub fn redo(&mut self) { // TODO: handle multiple cursors properly if let Some(pos) = self.buffer.redo() { @@ -311,16 +306,16 @@ impl Editor { self.cursors[0].range.0 = pos; self.cursors[0].range.1 = pos; self.cursors[0].update_vis_start(&(self.buffer), &(self.formatter)); - + self.move_view_to_cursor(); - + self.dirty = true; - + self.cursors.make_consistent(); } } - - + + /// Moves the editor's view the minimum amount to show the cursor pub fn move_view_to_cursor(&mut self) { // TODO: account for the horizontal offset of the editor view. @@ -328,335 +323,373 @@ impl Editor { // TODO: handle multiple cursors properly. Should only move if // there are no cursors currently in view, and should jump to // the closest cursor. - - // Find the first and last grapheme index visible within the editor. - let g_first = self.formatter.index_set_horizontal_v2d(&self.buffer, self.view_pos.0, 0, Floor); - let mut g_last = self.formatter.index_offset_vertical_v2d(&self.buffer, g_first, self.view_dim.0 as isize, (Floor, Floor)); - g_last = self.formatter.index_set_horizontal_v2d(&self.buffer, g_last, self.view_dim.1, Floor); - // Adjust the view depending on where the cursor is + // Find the first and last grapheme index visible within the editor. + let g_first = self.formatter + .index_set_horizontal_v2d(&self.buffer, self.view_pos.0, 0, Floor); + let mut g_last = self.formatter.index_offset_vertical_v2d(&self.buffer, + g_first, + self.view_dim.0 as isize, + (Floor, Floor)); + g_last = self.formatter + .index_set_horizontal_v2d(&self.buffer, g_last, self.view_dim.1, Floor); + + // Adjust the view depending on where the cursor is if self.cursors[0].range.0 < g_first { self.view_pos.0 = self.cursors[0].range.0; - } - else if self.cursors[0].range.0 > g_last { - self.view_pos.0 = self.formatter.index_offset_vertical_v2d(&self.buffer, self.cursors[0].range.0, -(self.view_dim.0 as isize), (Floor, Floor)); + } else if self.cursors[0].range.0 > g_last { + self.view_pos.0 = self.formatter.index_offset_vertical_v2d(&self.buffer, + self.cursors[0].range.0, + -(self.view_dim.0 as isize), + (Floor, Floor)); } } - - + + pub fn insert_text_at_cursor(&mut self, text: &str) { self.cursors.make_consistent(); - + let str_len = grapheme_count(text); let mut offset = 0; - + for c in self.cursors.iter_mut() { // Insert text self.buffer.insert_text(text, c.range.0 + offset); self.dirty = true; - + // Move cursor c.range.0 += str_len + offset; c.range.1 += str_len + offset; c.update_vis_start(&(self.buffer), &(self.formatter)); - + // Update offset offset += str_len; } - + // Adjust view self.move_view_to_cursor(); } - - + + pub fn insert_tab_at_cursor(&mut self) { 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.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 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 = ["", " ", " ", " ", " ", " ", " ", " ", " "]; + 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 { + } else { self.insert_text_at_cursor("\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}); + 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(); - + let mut offset = 0; - + for c in self.cursors.iter_mut() { // Update cursor with offset c.range.0 -= offset; c.range.1 -= offset; - + // Do nothing if there's nothing to delete. if c.range.0 == 0 { continue; } - + let len = min(c.range.0, grapheme_count); - + // Remove text self.buffer.remove_text_before(c.range.0, len); self.dirty = true; - + // Move cursor c.range.0 -= len; c.range.1 -= len; c.update_vis_start(&(self.buffer), &(self.formatter)); - + // Update offset offset += len; } - + self.cursors.make_consistent(); - + // Adjust view self.move_view_to_cursor(); } - - + + pub fn remove_text_in_front_of_cursor(&mut self, grapheme_count: usize) { self.cursors.make_consistent(); - + let mut offset = 0; - + for c in self.cursors.iter_mut() { // Update cursor with offset c.range.0 -= min(c.range.0, offset); c.range.1 -= min(c.range.1, offset); - + // Do nothing if there's nothing to delete. if c.range.1 == self.buffer.grapheme_count() { return; } - - let max_len = if self.buffer.grapheme_count() > c.range.1 {self.buffer.grapheme_count() - c.range.1} else {0}; + + let max_len = if self.buffer.grapheme_count() > c.range.1 { + self.buffer.grapheme_count() - c.range.1 + } else { + 0 + }; let len = min(max_len, grapheme_count); - + // Remove text self.buffer.remove_text_after(c.range.1, len); self.dirty = true; - + // Move cursor c.update_vis_start(&(self.buffer), &(self.formatter)); - + // Update offset offset += len; } - + self.cursors.make_consistent(); - + // Adjust view self.move_view_to_cursor(); } - - + + pub fn remove_text_inside_cursor(&mut self) { self.cursors.make_consistent(); - + let mut offset = 0; - + for c in self.cursors.iter_mut() { // Update cursor with offset c.range.0 -= min(c.range.0, offset); c.range.1 -= min(c.range.1, offset); - + // If selection, remove text if c.range.0 < c.range.1 { let len = c.range.1 - c.range.0; - + self.buffer.remove_text_before(c.range.0, c.range.1 - c.range.0); self.dirty = true; - + // Move cursor c.range.1 = c.range.0; - + // Update offset offset += len; } - + c.update_vis_start(&(self.buffer), &(self.formatter)); } - + self.cursors.make_consistent(); - + // Adjust view self.move_view_to_cursor(); } - - + + pub fn cursor_to_beginning_of_buffer(&mut self) { self.cursors = CursorSet::new(); - + self.cursors[0].range = (0, 0); self.cursors[0].update_vis_start(&(self.buffer), &(self.formatter)); - + // Adjust view self.move_view_to_cursor(); } - - + + pub fn cursor_to_end_of_buffer(&mut self) { let end = self.buffer.grapheme_count(); - + self.cursors = CursorSet::new(); self.cursors[0].range = (end, end); self.cursors[0].update_vis_start(&(self.buffer), &(self.formatter)); - + // Adjust view 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 { c.range.0 -= n; - } - else { + } else { c.range.0 = 0; } - + c.range.1 = c.range.0; c.update_vis_start(&(self.buffer), &(self.formatter)); } - + // Adjust view self.move_view_to_cursor(); } - - + + pub fn cursor_right(&mut self, n: usize) { for c in self.cursors.iter_mut() { c.range.1 += n; - + if c.range.1 > self.buffer.grapheme_count() { c.range.1 = self.buffer.grapheme_count(); } - + c.range.0 = c.range.1; c.update_vis_start(&(self.buffer), &(self.formatter)); } - + // Adjust view self.move_view_to_cursor(); } - - + + pub fn cursor_up(&mut self, n: usize) { 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)); - + let mut temp_index = self.formatter.index_offset_vertical_v2d(&self.buffer, + c.range.0, + vmove, + (Round, Round)); + 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); } - 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) { 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)); - + let mut temp_index = self.formatter.index_offset_vertical_v2d(&self.buffer, + c.range.0, + vmove, + (Round, Round)); + 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); } - 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 page_up(&mut self) { - let move_amount = self.view_dim.0 - max((self.view_dim.0 / 8), self.formatter.single_line_height()); - self.view_pos.0 = self.formatter.index_offset_vertical_v2d(&self.buffer, self.view_pos.0, -1 * move_amount as isize, (Round, Round)); - + let move_amount = self.view_dim.0 - + max((self.view_dim.0 / 8), self.formatter.single_line_height()); + self.view_pos.0 = self.formatter.index_offset_vertical_v2d(&self.buffer, + self.view_pos.0, + -1 * move_amount as isize, + (Round, Round)); + self.cursor_up(move_amount); - + // Adjust view self.move_view_to_cursor(); } - - + + pub fn page_down(&mut self) { - let move_amount = self.view_dim.0 - max((self.view_dim.0 / 8), self.formatter.single_line_height()); - self.view_pos.0 = self.formatter.index_offset_vertical_v2d(&self.buffer, self.view_pos.0, move_amount as isize, (Round, Round)); - + let move_amount = self.view_dim.0 - + max((self.view_dim.0 / 8), self.formatter.single_line_height()); + self.view_pos.0 = self.formatter.index_offset_vertical_v2d(&self.buffer, + self.view_pos.0, + move_amount as isize, + (Round, Round)); + self.cursor_down(move_amount); - + // Adjust view self.move_view_to_cursor(); } - - + + pub fn jump_to_line(&mut self, n: usize) { let pos = self.buffer.line_col_to_index((n, 0)); self.cursors.truncate(1); - self.cursors[0].range.0 = self.formatter.index_set_horizontal_v2d(&self.buffer, pos, self.cursors[0].vis_start, Round); + self.cursors[0].range.0 = self.formatter.index_set_horizontal_v2d(&self.buffer, + pos, + self.cursors[0] + .vis_start, + Round); self.cursors[0].range.1 = self.cursors[0].range.0; - + // Adjust view self.move_view_to_cursor(); } diff --git a/src/formatter.rs b/src/formatter.rs index fd142e0..666226c 100644 --- a/src/formatter.rs +++ b/src/formatter.rs @@ -20,147 +20,170 @@ pub enum RoundingBehavior { pub trait LineFormatter { fn single_line_height(&self) -> usize; - + /// Returns the 2d visual dimensions of the given text when formatted /// by the formatter. /// The text to be formatted is passed as a grapheme iterator. - fn dimensions<'a, T>(&'a self, g_iter: T) -> (usize, usize) - where T: Iterator; - - + fn dimensions<'a, T>(&'a self, g_iter: T) -> (usize, usize) where T: Iterator; + + /// Converts a grapheme index within a text into a visual 2d position. /// The text to be formatted is passed as a grapheme iterator. fn index_to_v2d<'a, T>(&'a self, g_iter: T, index: usize) -> (usize, usize) - where T: Iterator; - - + where T: Iterator; + + /// Converts a visual 2d position into a grapheme index within a text. /// The text to be formatted is passed as a grapheme iterator. - fn v2d_to_index<'a, T>(&'a self, g_iter: T, v2d: (usize, usize), rounding: (RoundingBehavior, RoundingBehavior)) -> usize - where T: Iterator; + fn v2d_to_index<'a, T>(&'a self, + g_iter: T, + v2d: (usize, usize), + rounding: (RoundingBehavior, RoundingBehavior)) + -> usize + where T: Iterator; 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); - + // Find the right block in the line, and the index within that block let (line_block, col_i_adjusted) = block_index_and_offset(col_i); - + // Get an iter into the right block let a = line_block * LINE_BLOCK_LENGTH; - let b = min(line.grapheme_count(), (line_block+1) * LINE_BLOCK_LENGTH); + let b = min(line.grapheme_count(), (line_block + 1) * LINE_BLOCK_LENGTH); let g_iter = line.grapheme_iter_between_indices(a, b); return self.index_to_v2d(g_iter, col_i_adjusted).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 { + fn index_offset_vertical_v2d(&self, + buf: &Buffer, + index: usize, + offset: isize, + rounding: (RoundingBehavior, RoundingBehavior)) + -> usize { // TODO: handle rounding modes // TODO: do this with bidirectional line iterator - + // Get the line and block index of the given index let (mut line_i, mut col_i) = buf.index_to_line_col(index); - + // Find the right block in the line, and the index within that block let (line_block, col_i_adjusted) = block_index_and_offset(col_i); - + let mut line = buf.get_line(line_i); - let (mut y, x) = self.index_to_v2d(line.grapheme_iter_between_indices(line_block * LINE_BLOCK_LENGTH, min(line.grapheme_count(), (line_block+1) * LINE_BLOCK_LENGTH)), col_i_adjusted); - + let (mut y, x) = + self.index_to_v2d(line.grapheme_iter_between_indices(line_block * LINE_BLOCK_LENGTH, + min(line.grapheme_count(), + (line_block + 1) * + LINE_BLOCK_LENGTH)), + col_i_adjusted); + // First, find the right line while keeping track of the vertical offset let mut new_y = y as isize + offset; - + let mut block_index: usize = line_block; loop { line = buf.get_line(line_i); - let (h, _) = self.dimensions(line.grapheme_iter_between_indices(block_index * LINE_BLOCK_LENGTH, min(line.grapheme_count(), (block_index+1) * LINE_BLOCK_LENGTH))); - + let (h, _) = + self.dimensions(line.grapheme_iter_between_indices(block_index * + LINE_BLOCK_LENGTH, + min(line.grapheme_count(), + (block_index + 1) * + LINE_BLOCK_LENGTH))); + if new_y >= 0 && new_y < h as isize { y = new_y as usize; break; - } - else { + } else { if new_y > 0 { let is_last_block = block_index >= last_block_index(line.grapheme_count()); - + // Check for off-the-end if is_last_block && (line_i + 1) >= buf.line_count() { return buf.grapheme_count(); } - - if is_last_block { + + if is_last_block { line_i += 1; block_index = 0; - } - else { + } else { block_index += 1; } new_y -= h as isize; - } - else if new_y < 0 { + } else if new_y < 0 { // Check for off-the-end if block_index == 0 && line_i == 0 { return 0; } - + if block_index == 0 { line_i -= 1; line = buf.get_line(line_i); block_index = last_block_index(line.grapheme_count()); - } - else { + } else { block_index -= 1; } let (h, _) = self.dimensions(line.grapheme_iter_between_indices(block_index * LINE_BLOCK_LENGTH, min(line.grapheme_count(), (block_index+1) * LINE_BLOCK_LENGTH))); new_y += h as isize; - } - else { + } else { unreachable!(); } } } - + // Next, convert the resulting coordinates back into buffer-wide // coordinates. - let block_slice = line.slice(block_index * LINE_BLOCK_LENGTH, min(line.grapheme_count(), (block_index+1) * LINE_BLOCK_LENGTH)); - let block_col_i = min(self.v2d_to_index(block_slice.grapheme_iter(), (y, x), rounding), LINE_BLOCK_LENGTH - 1); + let block_slice = line.slice(block_index * LINE_BLOCK_LENGTH, + min(line.grapheme_count(), + (block_index + 1) * LINE_BLOCK_LENGTH)); + let block_col_i = min(self.v2d_to_index(block_slice.grapheme_iter(), (y, x), rounding), + LINE_BLOCK_LENGTH - 1); col_i = (block_index * LINE_BLOCK_LENGTH) + block_col_i; - + return buf.line_col_to_index((line_i, col_i)); } - - + + /// Takes a grapheme index and a desired visual horizontal position, and /// returns a grapheme index on the same visual line as the given index, /// but offset to have the desired horizontal position. - fn index_set_horizontal_v2d(&self, buf: &Buffer, index: usize, horizontal: usize, rounding: RoundingBehavior) -> usize { + 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); - + // Find the right block in the line, and the index within that block let (line_block, col_i_adjusted) = block_index_and_offset(col_i); let start_index = line_block * LINE_BLOCK_LENGTH; - let end_index = min(line.grapheme_count(), start_index+LINE_BLOCK_LENGTH); - + let end_index = min(line.grapheme_count(), start_index + LINE_BLOCK_LENGTH); + // Calculate the horizontal position - let (v, _) = self.index_to_v2d(line.grapheme_iter_between_indices(start_index, end_index), col_i_adjusted); - let block_col_i = self.v2d_to_index(line.grapheme_iter_between_indices(start_index, end_index), (v, horizontal), (RoundingBehavior::Floor, rounding)); + let (v, _) = self.index_to_v2d(line.grapheme_iter_between_indices(start_index, end_index), + col_i_adjusted); + let block_col_i = self.v2d_to_index(line.grapheme_iter_between_indices(start_index, + end_index), + (v, horizontal), + (RoundingBehavior::Floor, rounding)); let mut new_col_i = start_index + min(block_col_i, LINE_BLOCK_LENGTH - 1); - + // Make sure we're not pushing the index off the end of the line - if (line_i + 1) < buf.line_count() - && new_col_i >= line.grapheme_count() - && line.grapheme_count() > 0 - { + if (line_i + 1) < buf.line_count() && new_col_i >= line.grapheme_count() && + line.grapheme_count() > 0 { new_col_i = line.grapheme_count() - 1; } - + return (index + new_col_i) - col_i; } - + } pub fn block_index_and_offset(index: usize) -> (usize, usize) { @@ -172,11 +195,10 @@ pub fn last_block_index(gc: usize) -> usize { if (gc % LINE_BLOCK_LENGTH) > 0 { block_count += 1; } - + if block_count > 0 { return block_count - 1; - } - else { + } else { return 0; } } diff --git a/src/main.rs b/src/main.rs index 16d133a..6a3ae00 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,6 @@ -//#![feature(test)] +// #![feature(test)] -extern crate time; -//extern crate test; +// extern crate test; extern crate rustbox; extern crate docopt; extern crate rustc_serialize; @@ -9,16 +8,16 @@ extern crate unicode_segmentation; extern crate unicode_width; extern crate encoding; extern crate ropey; -//extern crate freetype; -//extern crate sdl2; +// extern crate freetype; +// extern crate sdl2; use std::path::Path; use docopt::Docopt; use editor::Editor; use term_ui::TermUI; use term_ui::formatter::ConsoleLineFormatter; -//use gui::GUI; -//use gui::formatter::GUILineFormatter; +// use gui::GUI; +// use gui::formatter::GUILineFormatter; mod string_utils; mod utils; @@ -26,8 +25,8 @@ mod buffer; mod formatter; mod editor; mod term_ui; -//mod font; -//mod gui; +// mod font; +// mod gui; @@ -57,33 +56,31 @@ struct Args { fn main() { // Get command-line arguments 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 + // // Load file, if specified // let editor = if let Option::Some(s) = args.arg_file { // Editor::new_from_file(GUILineFormatter::new(4), &Path::new(&s[..])) // } // else { // Editor::new(GUILineFormatter::new(4)) // }; - // + // // // GUI // sdl2::init(sdl2::INIT_VIDEO); // let mut ui = GUI::new_from_editor(editor); // ui.main_ui_loop(); // sdl2::quit(); - } - else { - // Load file, if specified + } else { + // Load file, if specified let editor = if let Option::Some(s) = args.arg_file { Editor::new_from_file(ConsoleLineFormatter::new(4), &Path::new(&s[..])) - } - else { + } else { Editor::new(ConsoleLineFormatter::new(4)) }; - + // Console UI let mut ui = TermUI::new_from_editor(editor); ui.main_ui_loop(); diff --git a/src/string_utils.rs b/src/string_utils.rs index 46dfaf0..0006ecf 100644 --- a/src/string_utils.rs +++ b/src/string_utils.rs @@ -7,16 +7,16 @@ use unicode_segmentation::UnicodeSegmentation; pub fn is_line_ending(text: &str) -> bool { match text { - "\u{000D}\u{000A}" - | "\u{000A}" - | "\u{000B}" - | "\u{000C}" - | "\u{000D}" - | "\u{0085}" - | "\u{2028}" - | "\u{2029}" => true, - - _ => false + "\u{000D}\u{000A}" | + "\u{000A}" | + "\u{000B}" | + "\u{000C}" | + "\u{000D}" | + "\u{0085}" | + "\u{2028}" | + "\u{2029}" => true, + + _ => false, } } @@ -86,55 +86,55 @@ pub fn grapheme_count_is_less_than(text: &str, n: usize) -> bool { return false; } } - + return true; } pub fn grapheme_and_line_ending_count(text: &str) -> (usize, usize) { let mut grapheme_count = 0; let mut line_ending_count = 0; - + for g in UnicodeSegmentation::graphemes(text, true) { grapheme_count += 1; if is_line_ending(g) { line_ending_count += 1; } } - + return (grapheme_count, line_ending_count); } pub fn char_pos_to_byte_pos(text: &str, pos: usize) -> usize { let mut i: usize = 0; - + for (offset, _) in text.char_indices() { if i == pos { return offset; } i += 1; } - + if i == pos { return text.len(); } - + panic!("char_pos_to_byte_pos(): char position off the end of the string."); } pub fn grapheme_pos_to_byte_pos(text: &str, pos: usize) -> usize { let mut i: usize = 0; - + for (offset, _) in UnicodeSegmentation::grapheme_indices(text, true) { if i == pos { return offset; } i += 1; } - + if i == pos { return text.len(); } - + panic!("grapheme_pos_to_byte_pos(): grapheme position off the end of the string."); } @@ -142,13 +142,13 @@ pub fn grapheme_pos_to_byte_pos(text: &str, pos: usize) -> usize { pub fn insert_text_at_grapheme_index(s: &mut String, text: &str, pos: usize) { // Find insertion position in bytes let byte_pos = grapheme_pos_to_byte_pos(&s[..], pos); - + // Get byte vec of string let byte_vec = unsafe { s.as_mut_vec() }; - - // Grow data size + + // Grow data size byte_vec.extend(repeat(0).take(text.len())); - + // Move old bytes forward // TODO: use copy_memory()...? let mut from = byte_vec.len() - text.len(); @@ -156,15 +156,15 @@ pub fn insert_text_at_grapheme_index(s: &mut String, text: &str, pos: usize) { while from > byte_pos { from -= 1; to -= 1; - + byte_vec[to] = byte_vec[from]; } - + // Copy new bytes in // TODO: use copy_memory() let mut i = byte_pos; for g in UnicodeSegmentation::graphemes(text, true) { - + for b in g.bytes() { byte_vec[i] = b; i += 1 @@ -175,30 +175,31 @@ pub fn insert_text_at_grapheme_index(s: &mut String, text: &str, pos: usize) { /// Removes the text between the given grapheme indices in the given string. pub fn remove_text_between_grapheme_indices(s: &mut String, pos_a: usize, pos_b: usize) { // Bounds checks - assert!(pos_a <= pos_b, "remove_text_between_grapheme_indices(): pos_a must be less than or equal to pos_b."); - + assert!(pos_a <= pos_b, + "remove_text_between_grapheme_indices(): pos_a must be less than or equal to pos_b."); + if pos_a == pos_b { return; } - + // Find removal positions in bytes // TODO: get both of these in a single pass let byte_pos_a = grapheme_pos_to_byte_pos(&s[..], pos_a); let byte_pos_b = grapheme_pos_to_byte_pos(&s[..], pos_b); - + // Get byte vec of string let byte_vec = unsafe { s.as_mut_vec() }; - + // Move bytes to fill in the gap left by the removed bytes let mut from = byte_pos_b; let mut to = byte_pos_a; while from < byte_vec.len() { byte_vec[to] = byte_vec[from]; - + from += 1; to += 1; } - + // Remove data from the end let final_text_size = byte_vec.len() + byte_pos_a - byte_pos_b; byte_vec.truncate(final_text_size); @@ -209,18 +210,18 @@ pub fn remove_text_between_grapheme_indices(s: &mut String, pos_a: usize, pos_b: /// while the second section of the split is returned as a new string. pub fn split_string_at_grapheme_index(s1: &mut String, pos: usize) -> String { let mut s2 = String::new(); - + // Code block to contain the borrow of s2 { let byte_pos = grapheme_pos_to_byte_pos(&s1[..], pos); - + let byte_vec_1 = unsafe { s1.as_mut_vec() }; let byte_vec_2 = unsafe { s2.as_mut_vec() }; - + byte_vec_2.extend((&byte_vec_1[byte_pos..]).iter().cloned()); byte_vec_1.truncate(byte_pos); } - + return s2; } @@ -234,63 +235,63 @@ pub fn split_string_at_grapheme_index(s1: &mut String, pos: usize) -> String { /// Also acts as an index into `LINE_ENDINGS`. #[derive(PartialEq, Copy, Clone)] pub enum LineEnding { - None = 0, // No line ending - CRLF = 1, // CarriageReturn followed by LineFeed - LF = 2, // U+000A -- LineFeed - VT = 3, // U+000B -- VerticalTab - FF = 4, // U+000C -- FormFeed - CR = 5, // U+000D -- CarriageReturn - NEL = 6, // U+0085 -- NextLine - LS = 7, // U+2028 -- Line Separator - PS = 8, // U+2029 -- ParagraphSeparator + None = 0, // No line ending + CRLF = 1, // CarriageReturn followed by LineFeed + LF = 2, // U+000A -- LineFeed + VT = 3, // U+000B -- VerticalTab + FF = 4, // U+000C -- FormFeed + CR = 5, // U+000D -- CarriageReturn + NEL = 6, // U+0085 -- NextLine + LS = 7, // U+2028 -- Line Separator + PS = 8, // U+2029 -- ParagraphSeparator } pub fn str_to_line_ending(g: &str) -> LineEnding { match g { - //============== + // ============== // Line endings - //============== - + // ============== + // // CRLF "\u{000D}\u{000A}" => { return LineEnding::CRLF; - }, - + } + // LF "\u{000A}" => { return LineEnding::LF; - }, - + } + // VT "\u{000B}" => { return LineEnding::VT; - }, - + } + // FF "\u{000C}" => { return LineEnding::FF; - }, - + } + // CR "\u{000D}" => { return LineEnding::CR; - }, - + } + // NEL "\u{0085}" => { return LineEnding::NEL; - }, - + } + // LS "\u{2028}" => { return LineEnding::LS; - }, - + } + // PS "\u{2029}" => { return LineEnding::PS; - }, - + } + // Not a line ending _ => { return LineEnding::None; @@ -305,12 +306,11 @@ pub fn line_ending_to_str(ending: LineEnding) -> &'static str { /// An array of string literals corresponding to the possible /// unicode line endings. pub const LINE_ENDINGS: [&'static str; 9] = ["", - "\u{000D}\u{000A}", - "\u{000A}", - "\u{000B}", - "\u{000C}", - "\u{000D}", - "\u{0085}", - "\u{2028}", - "\u{2029}" -]; + "\u{000D}\u{000A}", + "\u{000A}", + "\u{000B}", + "\u{000C}", + "\u{000D}", + "\u{0085}", + "\u{2028}", + "\u{2029}"]; diff --git a/src/term_ui/formatter.rs b/src/term_ui/formatter.rs index 3676388..8203210 100644 --- a/src/term_ui/formatter.rs +++ b/src/term_ui/formatter.rs @@ -10,9 +10,9 @@ pub enum WrapType { WordWrap(usize), } -//=================================================================== +// =================================================================== // LineFormatter implementation for terminals/consoles. -//=================================================================== +// =================================================================== pub struct ConsoleLineFormatter { pub tab_width: u8, @@ -31,24 +31,23 @@ impl ConsoleLineFormatter { wrap_additional_indent: 0, } } - + pub fn set_wrap_width(&mut self, width: usize) { match self.wrap_type { - WrapType::NoWrap => { - }, - + WrapType::NoWrap => {} + WrapType::CharWrap(ref mut w) => { *w = width; - }, - + } + WrapType::WordWrap(ref mut w) => { *w = width; - }, + } } } - + pub fn iter<'a, T>(&'a self, g_iter: T) -> ConsoleLineFormatterVisIter<'a, T> - where T: Iterator + where T: Iterator { ConsoleLineFormatterVisIter::<'a, T> { grapheme_iter: g_iter, @@ -67,128 +66,135 @@ impl LineFormatter for ConsoleLineFormatter { fn single_line_height(&self) -> usize { return 1; } - - + + fn dimensions<'a, T>(&'a self, g_iter: T) -> (usize, usize) - where T: Iterator + where T: Iterator { let mut dim: (usize, usize) = (0, 0); - - for (_, pos, width) in self.iter(g_iter) { + + for (_, pos, width) in self.iter(g_iter) { dim = (max(dim.0, pos.0), max(dim.1, pos.1 + width)); } - + dim.0 += self.single_line_height(); - + return dim; } - - + + fn index_to_v2d<'a, T>(&'a self, g_iter: T, index: usize) -> (usize, usize) - where T: Iterator + where T: Iterator { let mut pos = (0, 0); let mut i = 0; let mut last_width = 0; - + for (_, _pos, width) in self.iter(g_iter) { pos = _pos; last_width = width; i += 1; - + if i > index { return pos; } } - + return (pos.0, pos.1 + last_width); } - - - fn v2d_to_index<'a, T>(&'a self, g_iter: T, v2d: (usize, usize), _: (RoundingBehavior, RoundingBehavior)) -> usize - where T: Iterator + + + fn v2d_to_index<'a, T>(&'a self, + g_iter: T, + v2d: (usize, usize), + _: (RoundingBehavior, RoundingBehavior)) + -> usize + where T: Iterator { // TODO: handle rounding modes let mut i = 0; - + for (_, pos, _) in self.iter(g_iter) { if pos.0 > v2d.0 { i -= 1; break; - } - else if pos.0 == v2d.0 && pos.1 >= v2d.1 { + } 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. -//=================================================================== +// =================================================================== pub struct ConsoleLineFormatterVisIter<'a, T> -where T: Iterator + where T: Iterator { grapheme_iter: T, f: &'a ConsoleLineFormatter, pos: (usize, usize), - + indent: usize, indent_found: bool, - + word_buf: Vec<&'a str>, word_i: usize, } impl<'a, T> ConsoleLineFormatterVisIter<'a, T> -where T: Iterator + where T: Iterator { fn next_nowrap(&mut self, g: &'a str) -> Option<(&'a str, (usize, usize), usize)> { let width = grapheme_vis_width_at_vis_pos(g, self.pos.1, self.f.tab_width as usize); - + let pos = self.pos; self.pos = (self.pos.0, self.pos.1 + width); return Some((g, pos, width)); } - - - fn next_charwrap(&mut self, g: &'a str, wrap_width: usize) -> Option<(&'a str, (usize, usize), usize)> { + + + fn next_charwrap(&mut self, + g: &'a str, + wrap_width: usize) + -> Option<(&'a str, (usize, usize), usize)> { let width = grapheme_vis_width_at_vis_pos(g, self.pos.1, self.f.tab_width as usize); - + if (self.pos.1 + width) > wrap_width { if !self.indent_found { self.indent = 0; self.indent_found = true; } - + if self.f.maintain_indent { - let pos = (self.pos.0 + self.f.single_line_height(), self.indent + self.f.wrap_additional_indent); - self.pos = (self.pos.0 + self.f.single_line_height(), self.indent + self.f.wrap_additional_indent + width); + let pos = (self.pos.0 + self.f.single_line_height(), + self.indent + self.f.wrap_additional_indent); + self.pos = (self.pos.0 + self.f.single_line_height(), + self.indent + self.f.wrap_additional_indent + width); + return Some((g, pos, width)); + } else { + let pos = (self.pos.0 + self.f.single_line_height(), + self.f.wrap_additional_indent); + self.pos = (self.pos.0 + self.f.single_line_height(), + self.f.wrap_additional_indent + width); return Some((g, pos, width)); } - else { - let pos = (self.pos.0 + self.f.single_line_height(), self.f.wrap_additional_indent); - self.pos = (self.pos.0 + self.f.single_line_height(), self.f.wrap_additional_indent + width); - return Some((g, pos, width)); - } - } - else { + } else { if !self.indent_found { if is_whitespace(g) { self.indent += width; - } - else { + } else { self.indent_found = true; } } - + let pos = self.pos; self.pos = (self.pos.0, self.pos.1 + width); return Some((g, pos, width)); @@ -199,7 +205,7 @@ where T: Iterator impl<'a, T> Iterator for ConsoleLineFormatterVisIter<'a, T> -where T: Iterator + where T: Iterator { type Item = (&'a str, (usize, usize), usize); @@ -208,21 +214,19 @@ where T: Iterator WrapType::NoWrap => { if let Some(g) = self.grapheme_iter.next() { return self.next_nowrap(g); - } - else { + } else { return None; } - }, - + } + WrapType::CharWrap(wrap_width) => { if let Some(g) = self.grapheme_iter.next() { return self.next_charwrap(g, wrap_width); - } - else { + } else { return None; } - }, - + } + WrapType::WordWrap(wrap_width) => { // Get next word if necessary if self.word_i >= self.word_buf.len() { @@ -230,52 +234,54 @@ where T: Iterator self.word_buf.truncate(0); while let Some(g) = self.grapheme_iter.next() { self.word_buf.push(g); - let width = grapheme_vis_width_at_vis_pos(g, self.pos.1 + word_width, self.f.tab_width as usize); + let width = grapheme_vis_width_at_vis_pos(g, + self.pos.1 + word_width, + self.f.tab_width as usize); word_width += width; if is_whitespace(g) { break; } } - + if self.word_buf.len() == 0 { return None; - } - else if !self.indent_found && !is_whitespace(self.word_buf[0]) { + } else if !self.indent_found && !is_whitespace(self.word_buf[0]) { self.indent_found = true; } - + // Move to next line if necessary if (self.pos.1 + word_width) > wrap_width { if !self.indent_found { self.indent = 0; self.indent_found = true; } - + if self.f.maintain_indent { - self.pos = (self.pos.0 + self.f.single_line_height(), self.indent + self.f.wrap_additional_indent); - } - else { - self.pos = (self.pos.0 + self.f.single_line_height(), self.f.wrap_additional_indent); + self.pos = (self.pos.0 + self.f.single_line_height(), + self.indent + self.f.wrap_additional_indent); + } else { + self.pos = (self.pos.0 + self.f.single_line_height(), + self.f.wrap_additional_indent); } } - + self.word_i = 0; } - + // Iterate over the word let g = self.word_buf[self.word_i]; self.word_i += 1; return self.next_charwrap(g, wrap_width); - }, + } } } } -//=================================================================== +// =================================================================== // Helper functions -//=================================================================== +// =================================================================== /// Returns the visual width of a grapheme given a starting /// position on a line. @@ -284,13 +290,12 @@ fn grapheme_vis_width_at_vis_pos(g: &str, pos: usize, tab_width: usize) -> usize "\t" => { let ending_pos = ((pos / tab_width) + 1) * tab_width; return ending_pos - pos; - }, - + } + _ => { if is_line_ending(g) { return 1; - } - else { + } else { return UnicodeWidthStr::width(g); } } @@ -307,317 +312,405 @@ mod tests { use formatter::{LineFormatter, LINE_BLOCK_LENGTH}; use formatter::RoundingBehavior::{Round, Floor, Ceiling}; use buffer::Buffer; - - + + #[test] fn dimensions_1() { let text = "Hello there, stranger!"; // 22 graphemes long - + let mut f = ConsoleLineFormatter::new(4); f.wrap_type = WrapType::CharWrap(0); f.maintain_indent = false; f.wrap_additional_indent = 0; f.set_wrap_width(80); - - assert_eq!(f.dimensions(UnicodeSegmentation::graphemes(text, true)), (1, 22)); + + assert_eq!(f.dimensions(UnicodeSegmentation::graphemes(text, true)), + (1, 22)); } - - + + #[test] fn dimensions_2() { let text = "Hello there, stranger! How are you doing this fine day?"; // 56 graphemes long - + let mut f = ConsoleLineFormatter::new(4); f.wrap_type = WrapType::CharWrap(0); f.maintain_indent = false; f.wrap_additional_indent = 0; f.set_wrap_width(12); - - assert_eq!(f.dimensions(UnicodeSegmentation::graphemes(text, true)), (5, 12)); + + assert_eq!(f.dimensions(UnicodeSegmentation::graphemes(text, true)), + (5, 12)); } - - + + #[test] fn index_to_v2d_1() { let text = "Hello there, stranger!"; // 22 graphemes long - + let mut f = ConsoleLineFormatter::new(4); f.wrap_type = WrapType::CharWrap(0); f.maintain_indent = false; f.wrap_additional_indent = 0; f.set_wrap_width(80); - - assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 0), (0, 0)); - assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 5), (0, 5)); - assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 22), (0, 22)); - assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 23), (0, 22)); + + assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 0), + (0, 0)); + assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 5), + (0, 5)); + assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 22), + (0, 22)); + assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 23), + (0, 22)); } - - + + #[test] fn index_to_v2d_2() { let text = "Hello there, stranger! How are you doing this fine day?"; // 56 graphemes long - + let mut f = ConsoleLineFormatter::new(4); f.wrap_type = WrapType::CharWrap(0); f.maintain_indent = false; f.wrap_additional_indent = 0; f.set_wrap_width(12); - - assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 0), (0, 0)); - assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 5), (0, 5)); - assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 11), (0, 11)); - - assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 12), (1, 0)); - assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 15), (1, 3)); - assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 23), (1, 11)); - - assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 24), (2, 0)); - assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 28), (2, 4)); - assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 35), (2, 11)); - - assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 36), (3, 0)); - assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 43), (3, 7)); - assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 47), (3, 11)); - - assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 48), (4, 0)); - assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 50), (4, 2)); - assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 56), (4, 8)); - - assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 57), (4, 8)); + + assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 0), + (0, 0)); + assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 5), + (0, 5)); + assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 11), + (0, 11)); + + assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 12), + (1, 0)); + assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 15), + (1, 3)); + assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 23), + (1, 11)); + + assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 24), + (2, 0)); + assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 28), + (2, 4)); + assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 35), + (2, 11)); + + assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 36), + (3, 0)); + assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 43), + (3, 7)); + assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 47), + (3, 11)); + + assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 48), + (4, 0)); + assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 50), + (4, 2)); + assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 56), + (4, 8)); + + assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 57), + (4, 8)); } - - + + #[test] fn v2d_to_index_1() { let text = "Hello there, stranger!"; // 22 graphemes long - + let mut f = ConsoleLineFormatter::new(4); f.wrap_type = WrapType::CharWrap(0); f.maintain_indent = false; f.wrap_additional_indent = 0; f.set_wrap_width(80); - - assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true), (0,0), (Floor, Floor)), 0); - assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true), (0,5), (Floor, Floor)), 5); - assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true), (0,22), (Floor, Floor)), 22); - assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true), (0,23), (Floor, Floor)), 22); - assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true), (1,0), (Floor, Floor)), 22); - assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true), (1,1), (Floor, Floor)), 22); + + assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true), + (0, 0), + (Floor, Floor)), + 0); + assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true), + (0, 5), + (Floor, Floor)), + 5); + assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true), + (0, 22), + (Floor, Floor)), + 22); + assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true), + (0, 23), + (Floor, Floor)), + 22); + assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true), + (1, 0), + (Floor, Floor)), + 22); + assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true), + (1, 1), + (Floor, Floor)), + 22); } - - + + #[test] fn v2d_to_index_2() { let text = "Hello there, stranger! How are you doing this fine day?"; // 56 graphemes long - + let mut f = ConsoleLineFormatter::new(4); f.wrap_type = WrapType::CharWrap(0); f.maintain_indent = false; f.wrap_additional_indent = 0; f.set_wrap_width(12); - - assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true), (0,0), (Floor, Floor)), 0); - assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true), (0,11), (Floor, Floor)), 11); - assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true), (0,12), (Floor, Floor)), 11); - - assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true), (1,0), (Floor, Floor)), 12); - assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true), (1,11), (Floor, Floor)), 23); - assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true), (1,12), (Floor, Floor)), 23); - - assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true), (2,0), (Floor, Floor)), 24); - assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true), (2,11), (Floor, Floor)), 35); - assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true), (2,12), (Floor, Floor)), 35); - - assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true), (3,0), (Floor, Floor)), 36); - assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true), (3,11), (Floor, Floor)), 47); - assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true), (3,12), (Floor, Floor)), 47); - - assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true), (4,0), (Floor, Floor)), 48); - assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true), (4,7), (Floor, Floor)), 55); - assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true), (4,8), (Floor, Floor)), 56); - assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true), (4,9), (Floor, Floor)), 56); + + assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true), + (0, 0), + (Floor, Floor)), + 0); + assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true), + (0, 11), + (Floor, Floor)), + 11); + assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true), + (0, 12), + (Floor, Floor)), + 11); + + assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true), + (1, 0), + (Floor, Floor)), + 12); + assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true), + (1, 11), + (Floor, Floor)), + 23); + assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true), + (1, 12), + (Floor, Floor)), + 23); + + assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true), + (2, 0), + (Floor, Floor)), + 24); + assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true), + (2, 11), + (Floor, Floor)), + 35); + assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true), + (2, 12), + (Floor, Floor)), + 35); + + assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true), + (3, 0), + (Floor, Floor)), + 36); + assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true), + (3, 11), + (Floor, Floor)), + 47); + assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true), + (3, 12), + (Floor, Floor)), + 47); + + assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true), + (4, 0), + (Floor, Floor)), + 48); + assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true), + (4, 7), + (Floor, Floor)), + 55); + assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true), + (4, 8), + (Floor, Floor)), + 56); + assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true), + (4, 9), + (Floor, Floor)), + 56); } - - + + #[test] fn index_to_horizontal_v2d_1() { let b = Buffer::new_from_str("Hello there, stranger!\nHow are you doing this fine day?"); // 55 graphemes long - + let mut f = ConsoleLineFormatter::new(4); f.wrap_type = WrapType::CharWrap(0); f.maintain_indent = false; f.wrap_additional_indent = 0; f.set_wrap_width(80); - + assert_eq!(f.index_to_horizontal_v2d(&b, 0), 0); assert_eq!(f.index_to_horizontal_v2d(&b, 5), 5); assert_eq!(f.index_to_horizontal_v2d(&b, 26), 3); assert_eq!(f.index_to_horizontal_v2d(&b, 55), 32); assert_eq!(f.index_to_horizontal_v2d(&b, 56), 32); } - - + + #[test] fn index_to_horizontal_v2d_2() { let b = Buffer::new_from_str("Hello there, stranger!\nHow are you doing this fine day?"); // 55 graphemes long - + let mut f = ConsoleLineFormatter::new(4); f.wrap_type = WrapType::CharWrap(0); f.maintain_indent = false; f.wrap_additional_indent = 0; f.set_wrap_width(12); - + assert_eq!(f.index_to_horizontal_v2d(&b, 0), 0); assert_eq!(f.index_to_horizontal_v2d(&b, 11), 11); - + assert_eq!(f.index_to_horizontal_v2d(&b, 12), 0); assert_eq!(f.index_to_horizontal_v2d(&b, 22), 10); - + assert_eq!(f.index_to_horizontal_v2d(&b, 23), 0); assert_eq!(f.index_to_horizontal_v2d(&b, 34), 11); - + assert_eq!(f.index_to_horizontal_v2d(&b, 35), 0); assert_eq!(f.index_to_horizontal_v2d(&b, 46), 11); - + assert_eq!(f.index_to_horizontal_v2d(&b, 47), 0); assert_eq!(f.index_to_horizontal_v2d(&b, 55), 8); assert_eq!(f.index_to_horizontal_v2d(&b, 56), 8); } - - + + #[test] fn index_set_horizontal_v2d_1() { let b = Buffer::new_from_str("Hello there, stranger!\nHow are you doing this fine day?"); // 55 graphemes long - + let mut f = ConsoleLineFormatter::new(4); f.wrap_type = WrapType::CharWrap(0); f.maintain_indent = false; f.wrap_additional_indent = 0; f.set_wrap_width(80); - + assert_eq!(f.index_set_horizontal_v2d(&b, 0, 0, Floor), 0); assert_eq!(f.index_set_horizontal_v2d(&b, 0, 22, Floor), 22); assert_eq!(f.index_set_horizontal_v2d(&b, 0, 23, Floor), 22); - + assert_eq!(f.index_set_horizontal_v2d(&b, 8, 0, Floor), 0); assert_eq!(f.index_set_horizontal_v2d(&b, 8, 22, Floor), 22); assert_eq!(f.index_set_horizontal_v2d(&b, 8, 23, Floor), 22); - + assert_eq!(f.index_set_horizontal_v2d(&b, 22, 0, Floor), 0); assert_eq!(f.index_set_horizontal_v2d(&b, 22, 22, Floor), 22); assert_eq!(f.index_set_horizontal_v2d(&b, 22, 23, Floor), 22); - + assert_eq!(f.index_set_horizontal_v2d(&b, 23, 0, Floor), 23); assert_eq!(f.index_set_horizontal_v2d(&b, 23, 32, Floor), 55); assert_eq!(f.index_set_horizontal_v2d(&b, 23, 33, Floor), 55); - + assert_eq!(f.index_set_horizontal_v2d(&b, 28, 0, Floor), 23); assert_eq!(f.index_set_horizontal_v2d(&b, 28, 32, Floor), 55); assert_eq!(f.index_set_horizontal_v2d(&b, 28, 33, Floor), 55); - + assert_eq!(f.index_set_horizontal_v2d(&b, 55, 0, Floor), 23); assert_eq!(f.index_set_horizontal_v2d(&b, 55, 32, Floor), 55); assert_eq!(f.index_set_horizontal_v2d(&b, 55, 33, Floor), 55); } - - + + #[test] fn index_set_horizontal_v2d_2() { let b = Buffer::new_from_str("Hello there, stranger! How are you doing this fine day?"); // 55 graphemes long - + let mut f = ConsoleLineFormatter::new(4); f.wrap_type = WrapType::CharWrap(0); f.maintain_indent = false; f.wrap_additional_indent = 0; f.set_wrap_width(12); - + assert_eq!(f.index_set_horizontal_v2d(&b, 0, 0, Floor), 0); assert_eq!(f.index_set_horizontal_v2d(&b, 0, 11, Floor), 11); assert_eq!(f.index_set_horizontal_v2d(&b, 0, 12, Floor), 11); - + assert_eq!(f.index_set_horizontal_v2d(&b, 8, 0, Floor), 0); assert_eq!(f.index_set_horizontal_v2d(&b, 8, 11, Floor), 11); assert_eq!(f.index_set_horizontal_v2d(&b, 8, 12, Floor), 11); - + assert_eq!(f.index_set_horizontal_v2d(&b, 11, 0, Floor), 0); assert_eq!(f.index_set_horizontal_v2d(&b, 11, 11, Floor), 11); assert_eq!(f.index_set_horizontal_v2d(&b, 11, 12, Floor), 11); - + assert_eq!(f.index_set_horizontal_v2d(&b, 12, 0, Floor), 12); assert_eq!(f.index_set_horizontal_v2d(&b, 12, 11, Floor), 23); assert_eq!(f.index_set_horizontal_v2d(&b, 12, 12, Floor), 23); - + assert_eq!(f.index_set_horizontal_v2d(&b, 17, 0, Floor), 12); assert_eq!(f.index_set_horizontal_v2d(&b, 17, 11, Floor), 23); assert_eq!(f.index_set_horizontal_v2d(&b, 17, 12, Floor), 23); - + assert_eq!(f.index_set_horizontal_v2d(&b, 23, 0, Floor), 12); assert_eq!(f.index_set_horizontal_v2d(&b, 23, 11, Floor), 23); assert_eq!(f.index_set_horizontal_v2d(&b, 23, 12, Floor), 23); } - - + + #[test] fn index_offset_vertical_v2d_1() { let b = Buffer::new_from_str("Hello there, stranger!\nHow are you doing this fine day?"); // 55 graphemes long - + let mut f = ConsoleLineFormatter::new(4); f.wrap_type = WrapType::CharWrap(0); f.maintain_indent = false; f.wrap_additional_indent = 0; f.set_wrap_width(80); - + assert_eq!(f.index_offset_vertical_v2d(&b, 0, 0, (Floor, Floor)), 0); assert_eq!(f.index_offset_vertical_v2d(&b, 0, 1, (Floor, Floor)), 23); assert_eq!(f.index_offset_vertical_v2d(&b, 23, -1, (Floor, Floor)), 0); - + assert_eq!(f.index_offset_vertical_v2d(&b, 2, 0, (Floor, Floor)), 2); assert_eq!(f.index_offset_vertical_v2d(&b, 2, 1, (Floor, Floor)), 25); assert_eq!(f.index_offset_vertical_v2d(&b, 25, -1, (Floor, Floor)), 2); - + assert_eq!(f.index_offset_vertical_v2d(&b, 22, 0, (Floor, Floor)), 22); assert_eq!(f.index_offset_vertical_v2d(&b, 22, 1, (Floor, Floor)), 45); assert_eq!(f.index_offset_vertical_v2d(&b, 45, -1, (Floor, Floor)), 22); - + assert_eq!(f.index_offset_vertical_v2d(&b, 54, 0, (Floor, Floor)), 54); assert_eq!(f.index_offset_vertical_v2d(&b, 54, 1, (Floor, Floor)), 55); assert_eq!(f.index_offset_vertical_v2d(&b, 54, -1, (Floor, Floor)), 22); } - - + + #[test] fn index_offset_vertical_v2d_2() { let b = Buffer::new_from_str("Hello there, stranger! How are you doing this fine day?"); // 55 graphemes long - + let mut f = ConsoleLineFormatter::new(4); f.wrap_type = WrapType::CharWrap(0); f.maintain_indent = false; f.wrap_additional_indent = 0; f.set_wrap_width(12); - + assert_eq!(f.index_offset_vertical_v2d(&b, 0, 0, (Floor, Floor)), 0); assert_eq!(f.index_offset_vertical_v2d(&b, 0, 1, (Floor, Floor)), 12); assert_eq!(f.index_offset_vertical_v2d(&b, 0, 2, (Floor, Floor)), 24); - + assert_eq!(f.index_offset_vertical_v2d(&b, 0, 0, (Floor, Floor)), 0); assert_eq!(f.index_offset_vertical_v2d(&b, 12, -1, (Floor, Floor)), 0); assert_eq!(f.index_offset_vertical_v2d(&b, 24, -2, (Floor, Floor)), 0); - + assert_eq!(f.index_offset_vertical_v2d(&b, 4, 0, (Floor, Floor)), 4); assert_eq!(f.index_offset_vertical_v2d(&b, 4, 1, (Floor, Floor)), 16); assert_eq!(f.index_offset_vertical_v2d(&b, 4, 2, (Floor, Floor)), 28); - + assert_eq!(f.index_offset_vertical_v2d(&b, 4, 0, (Floor, Floor)), 4); assert_eq!(f.index_offset_vertical_v2d(&b, 16, -1, (Floor, Floor)), 4); assert_eq!(f.index_offset_vertical_v2d(&b, 28, -2, (Floor, Floor)), 4); - + assert_eq!(f.index_offset_vertical_v2d(&b, 11, 0, (Floor, Floor)), 11); assert_eq!(f.index_offset_vertical_v2d(&b, 11, 1, (Floor, Floor)), 23); assert_eq!(f.index_offset_vertical_v2d(&b, 11, 2, (Floor, Floor)), 35); - + assert_eq!(f.index_offset_vertical_v2d(&b, 11, 0, (Floor, Floor)), 11); assert_eq!(f.index_offset_vertical_v2d(&b, 23, -1, (Floor, Floor)), 11); assert_eq!(f.index_offset_vertical_v2d(&b, 35, -2, (Floor, Floor)), 11); } -} \ No newline at end of file +} diff --git a/src/term_ui/mod.rs b/src/term_ui/mod.rs index bcc0d75..8802528 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 time::Duration; +use std::time::Duration; use formatter::{LineFormatter, LINE_BLOCK_LENGTH, block_index_and_offset}; use std::char; use std::default::Default; @@ -55,8 +55,8 @@ impl TermUI { let w = rb.width(); let h = rb.height(); let mut editor = Editor::new(ConsoleLineFormatter::new(4)); - editor.update_dim(h-1, w); - + editor.update_dim(h - 1, w); + TermUI { rb: rb, editor: editor, @@ -64,7 +64,7 @@ impl TermUI { height: h, } } - + pub fn new_from_editor(ed: Editor) -> TermUI { let rb = match rustbox::RustBox::init(rustbox::InitOptions { buffer_stderr: false, @@ -76,8 +76,8 @@ impl TermUI { let w = rb.width(); let h = rb.height(); let mut editor = ed; - editor.update_dim(h-1, w); - + editor.update_dim(h - 1, w); + TermUI { rb: rb, editor: editor, @@ -85,24 +85,24 @@ impl TermUI { height: h, } } - + pub fn main_ui_loop(&mut self) { // Quitting flag let mut quit = false; let mut resize: Option<(usize, usize)> = None; - - self.editor.update_dim(self.height-1, self.width); + + self.editor.update_dim(self.height - 1, self.width); self.editor.formatter.set_wrap_width(self.width as usize); - + loop { // Draw the editor to screen self.editor.update_view_dim(); self.editor.formatter.set_wrap_width(self.editor.view_dim.1); self.rb.clear(); - self.draw_editor(&self.editor, (0, 0), (self.height-1, self.width-1)); + self.draw_editor(&self.editor, (0, 0), (self.height - 1, self.width - 1)); self.rb.present(); - - + + // Handle events. We block on the first event, so that the // program doesn't loop like crazy, but then continue pulling // events in a non-blocking way until we run out of events @@ -111,135 +111,140 @@ impl TermUI { loop { match e { Ok(rustbox::Event::KeyEventRaw(_, key, character)) => { - //println!(" {} {} {}", modifier, key, character); + // println!(" {} {} {}", modifier, key, character); match key { K_CTRL_Q => { quit = true; break; - }, - + } + K_CTRL_S => { self.editor.save_if_dirty(); - }, - + } + K_CTRL_Z => { self.editor.undo(); - }, - + } + K_CTRL_Y => { self.editor.redo(); - }, - + } + K_CTRL_L => { self.go_to_line_ui_loop(); - }, - + } + K_PAGEUP => { self.editor.page_up(); - }, - + } + K_PAGEDOWN => { self.editor.page_down(); - }, - + } + K_UP => { self.editor.cursor_up(1); - }, - + } + K_DOWN => { self.editor.cursor_down(1); - }, - + } + K_LEFT => { self.editor.cursor_left(1); - }, - + } + K_RIGHT => { self.editor.cursor_right(1); - }, - + } + K_ENTER => { let nl = line_ending_to_str(self.editor.line_ending_type); self.editor.insert_text_at_cursor(nl); - }, - + } + K_SPACE => { self.editor.insert_text_at_cursor(" "); - }, - + } + K_TAB => { self.editor.insert_tab_at_cursor(); - }, - + } + K_BACKSPACE => { self.editor.backspace_at_cursor(); - }, - + } + K_DELETE => { self.editor.remove_text_in_front_of_cursor(1); - }, - + } + // Character 0 => { if let Option::Some(c) = char::from_u32(character) { self.editor.insert_text_at_cursor(&c.to_string()[..]); } - }, - + } + _ => {} } - }, - + } + Ok(rustbox::Event::ResizeEvent(w, h)) => { resize = Some((h as usize, w as usize)); - }, - + } + _ => { break; } } - - e = self.rb.peek_event(Duration::milliseconds(0), true); // Get next event (if any) + + e = self.rb.peek_event(Duration::from_millis(0), true); // Get next event (if any) } - + if let Some((h, w)) = resize { self.width = w as usize; self.height = h as usize; self.editor.update_dim(self.height, self.width); } resize = None; - + // Quit if quit flag is set if quit { break; } } } - - + + fn go_to_line_ui_loop(&mut self) { let foreground = Color::Black; let background = Color::Cyan; - + let mut cancel = false; let mut confirm = false; let prefix = "Jump to line: "; let mut line = String::new(); - + loop { // Draw the editor to screen self.editor.update_view_dim(); self.editor.formatter.set_wrap_width(self.editor.view_dim.1); self.rb.clear(); - self.draw_editor(&self.editor, (0, 0), (self.height-1, self.width-1)); + self.draw_editor(&self.editor, (0, 0), (self.height - 1, self.width - 1)); for i in 0..self.width { self.rb.print(i, 0, rustbox::RB_NORMAL, foreground, background, " "); } self.rb.print(1, 0, rustbox::RB_NORMAL, foreground, background, prefix); - self.rb.print(prefix.len() + 1, 0, rustbox::RB_NORMAL, foreground, background, &line[..]); + self.rb.print(prefix.len() + 1, + 0, + rustbox::RB_NORMAL, + foreground, + background, + &line[..]); self.rb.present(); - - + + // Handle events. We block on the first event, so that the // program doesn't loop like crazy, but then continue pulling // events in a non-blocking way until we run out of events @@ -252,17 +257,17 @@ impl TermUI { K_ESC => { cancel = true; break; - }, - + } + K_ENTER => { confirm = true; break; - }, - + } + K_BACKSPACE => { line.pop(); - }, - + } + // Character 0 => { if let Option::Some(c) = char::from_u32(character) { @@ -270,40 +275,39 @@ impl TermUI { line.push(c); } } - }, - + } + _ => {} } - }, - + } + Ok(rustbox::Event::ResizeEvent(w, h)) => { self.width = w as usize; self.height = h as usize; - self.editor.update_dim(self.height-1, self.width); - }, - + self.editor.update_dim(self.height - 1, self.width); + } + _ => { break; } } - - e = self.rb.peek_event(Duration::milliseconds(0), true); // Get next event (if any) + + e = self.rb.peek_event(Duration::from_millis(0), true); // Get next event (if any) } - - + + // Cancel if flag is set if cancel { break; } - + // Jump to line! if confirm { 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); - } - else { + self.editor.jump_to_line(n2 - 1); + } else { self.editor.jump_to_line(0); } } @@ -311,35 +315,52 @@ impl TermUI { } } } - - - fn draw_editor(&self, editor: &Editor, c1: (usize, usize), c2: (usize, usize)) { + + + fn draw_editor(&self, + editor: &Editor, + c1: (usize, usize), + c2: (usize, usize)) { let foreground = Color::Black; let background = Color::Cyan; - + // Fill in top row with info line color for i in c1.1..(c2.1 + 1) { self.rb.print(i, c1.0, rustbox::RB_NORMAL, foreground, background, " "); } - + // Filename and dirty marker let filename = editor.file_path.display(); - let dirty_char = if editor.dirty {"*"} else {""}; + let dirty_char = if editor.dirty { + "*" + } else { + "" + }; let name = format!("{}{}", filename, dirty_char); - self.rb.print(c1.1 + 1, c1.0, rustbox::RB_NORMAL, foreground, background, &name[..]); - + self.rb.print(c1.1 + 1, + c1.0, + rustbox::RB_NORMAL, + foreground, + background, + &name[..]); + // Percentage position in document // TODO: use view instead of cursor for calculation if there is more // than one cursor. let percentage: usize = if editor.buffer.grapheme_count() > 0 { - (((editor.cursors[0].range.0 as f32) / (editor.buffer.grapheme_count() as f32)) * 100.0) as usize - } - else { + (((editor.cursors[0].range.0 as f32) / (editor.buffer.grapheme_count() as f32)) * + 100.0) as usize + } else { 100 }; let pstring = format!("{}%", percentage); - self.rb.print(c2.1 - pstring.len(), c1.0, rustbox::RB_NORMAL, foreground, background, &pstring[..]); - + self.rb.print(c2.1 - pstring.len(), + c1.0, + rustbox::RB_NORMAL, + foreground, + background, + &pstring[..]); + // Text encoding info and tab style let nl = match editor.line_ending_type { LineEnding::None => "None", @@ -352,34 +373,51 @@ impl TermUI { LineEnding::LS => "LS", LineEnding::PS => "PS", }; - let soft_tabs_str = if editor.soft_tabs {"spaces"} else {"tabs"}; - let info_line = format!("UTF8:{} {}:{}", nl, soft_tabs_str, editor.soft_tab_width as usize); - self.rb.print(c2.1 - 30, c1.0, rustbox::RB_NORMAL, foreground, background, &info_line[..]); + let soft_tabs_str = if editor.soft_tabs { + "spaces" + } else { + "tabs" + }; + let info_line = format!("UTF8:{} {}:{}", + nl, + soft_tabs_str, + editor.soft_tab_width as usize); + self.rb.print(c2.1 - 30, + c1.0, + rustbox::RB_NORMAL, + foreground, + background, + &info_line[..]); // Draw main text editing area self.draw_editor_text(editor, (c1.0 + 1, c1.1), c2); } - fn draw_editor_text(&self, editor: &Editor, c1: (usize, usize), c2: (usize, usize)) { + fn draw_editor_text(&self, + editor: &Editor, + c1: (usize, usize), + c2: (usize, usize)) { // Calculate all the starting info let gutter_width = editor.editor_dim.1 - editor.view_dim.1; let (line_index, col_i) = editor.buffer.index_to_line_col(editor.view_pos.0); let (mut line_block_index, _) = block_index_and_offset(col_i); - let mut grapheme_index = editor.buffer.line_col_to_index((line_index, line_block_index * LINE_BLOCK_LENGTH)); + let mut grapheme_index = editor.buffer.line_col_to_index((line_index, + line_block_index * + LINE_BLOCK_LENGTH)); let temp_line = editor.buffer.get_line(line_index); let (vis_line_offset, _) = editor.formatter.index_to_v2d(temp_line.grapheme_iter_between_indices(line_block_index*LINE_BLOCK_LENGTH, min(temp_line.grapheme_count(), (line_block_index+1)*LINE_BLOCK_LENGTH)), editor.view_pos.0 - grapheme_index); - + let mut screen_line = c1.0 as isize - vis_line_offset as isize; let screen_col = c1.1 as isize + gutter_width as isize; - + // Fill in the gutter with the appropriate background - for y in c1.0..(c2.0+1) { - for x in c1.1..(c1.1+gutter_width-1) { + for y in c1.0..(c2.0 + 1) { + for x in c1.1..(c1.1 + gutter_width - 1) { self.rb.print(x, y, rustbox::RB_NORMAL, Color::White, Color::Blue, " "); } } - + let mut line_num = line_index + 1; for line in editor.buffer.line_iter_at_index(line_index) { // Print line number @@ -387,17 +425,23 @@ impl TermUI { let lnx = c1.1 + (gutter_width - 1 - digit_count(line_num as u32, 10) as usize); let lny = screen_line as usize; if lny >= c1.0 && lny <= c2.0 { - self.rb.print(lnx, lny, rustbox::RB_NORMAL, Color::White, Color::Blue, &format!("{}", line_num)[..]); + self.rb.print(lnx, + lny, + rustbox::RB_NORMAL, + Color::White, + Color::Blue, + &format!("{}", line_num)[..]); } } - + // Loop through the graphemes of the line and print them to // the screen. let mut line_g_index: usize = 0; let mut last_pos_y = 0; let mut lines_traversed: usize = 0; - let mut g_iter = editor.formatter.iter(line.grapheme_iter_at_index(line_block_index*LINE_BLOCK_LENGTH)); - + let mut g_iter = editor.formatter.iter(line.grapheme_iter_at_index(line_block_index * + LINE_BLOCK_LENGTH)); + loop { if let Some((g, (pos_y, pos_x), width)) = g_iter.next() { if last_pos_y != pos_y { @@ -409,12 +453,12 @@ impl TermUI { // Calculate the cell coordinates at which to draw the grapheme let px = pos_x as isize + screen_col - editor.view_pos.1 as isize; let py = lines_traversed as isize + screen_line; - + // If we're off the bottom, we're done if py > c2.0 as isize { return; } - + // Draw the grapheme to the screen if it's in bounds if (px >= c1.1 as isize) && (py >= c1.0 as isize) && (px <= c2.1 as isize) { // Check if the character is within a cursor @@ -424,60 +468,82 @@ impl TermUI { at_cursor = true; } } - + // Actually print the character if is_line_ending(g) { if at_cursor { - self.rb.print(px as usize, py as usize, rustbox::RB_NORMAL, Color::Black, Color::White, " "); + self.rb.print(px as usize, + py as usize, + rustbox::RB_NORMAL, + Color::Black, + Color::White, + " "); } - } - else if g == "\t" { + } else if g == "\t" { for i in 0..width { let tpx = px as usize + i; if tpx <= c2.1 { - self.rb.print(tpx as usize, py as usize, rustbox::RB_NORMAL, Color::White, Color::Black, " "); + self.rb.print(tpx as usize, + py as usize, + rustbox::RB_NORMAL, + Color::White, + Color::Black, + " "); } } - + if at_cursor { - self.rb.print(px as usize, py as usize, rustbox::RB_NORMAL, Color::Black, Color::White, " "); + self.rb.print(px as usize, + py as usize, + rustbox::RB_NORMAL, + Color::Black, + Color::White, + " "); } - } - else { + } else { if at_cursor { - self.rb.print(px as usize, py as usize, rustbox::RB_NORMAL, Color::Black, Color::White, g); - } - else { - self.rb.print(px as usize, py as usize, rustbox::RB_NORMAL, Color::White, Color::Black, g); + self.rb.print(px as usize, + py as usize, + rustbox::RB_NORMAL, + Color::Black, + Color::White, + g); + } else { + self.rb.print(px as usize, + py as usize, + rustbox::RB_NORMAL, + Color::White, + Color::Black, + g); } } } - } - else { + } else { break; } - + grapheme_index += 1; line_g_index += 1; - + if line_g_index >= LINE_BLOCK_LENGTH { line_block_index += 1; line_g_index = 0; - g_iter = editor.formatter.iter(line.grapheme_iter_at_index(line_block_index * LINE_BLOCK_LENGTH)); + g_iter = editor.formatter.iter(line.grapheme_iter_at_index(line_block_index * + LINE_BLOCK_LENGTH)); lines_traversed += 1; } } - + line_block_index = 0; - screen_line += lines_traversed as isize + 1; + screen_line += lines_traversed as isize + 1; line_num += 1; } - + // If we get here, it means we reached the end of the text buffer // without going off the bottom of the screen. So draw the cursor // at the end if needed. - + // Check if the character is within a cursor let mut at_cursor = false; for c in editor.cursors.iter() { @@ -485,18 +551,25 @@ impl TermUI { at_cursor = true; } } - + if at_cursor { // Calculate the cell coordinates at which to draw the cursor - let pos_x = editor.formatter.index_to_horizontal_v2d(&self.editor.buffer, self.editor.buffer.grapheme_count()); + let pos_x = editor.formatter.index_to_horizontal_v2d(&self.editor.buffer, + self.editor + .buffer + .grapheme_count()); let px = pos_x as isize + screen_col - editor.view_pos.1 as isize; let py = screen_line - 1; - - if (px >= c1.1 as isize) && (py >= c1.0 as isize) && (px <= c2.1 as isize) && (py <= c2.0 as isize) { - self.rb.print(px as usize, py as usize, rustbox::RB_NORMAL, Color::Black, Color::White, " "); + + if (px >= c1.1 as isize) && (py >= c1.0 as isize) && (px <= c2.1 as isize) && + (py <= c2.0 as isize) { + self.rb.print(px as usize, + py as usize, + rustbox::RB_NORMAL, + Color::Black, + Color::White, + " "); } } } - - -} \ No newline at end of file +} diff --git a/src/utils.rs b/src/utils.rs index c9adb10..9e68735 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -13,7 +13,7 @@ pub fn digit_count(mut n: u32, b: u32) -> u32 { #[cfg(test)] mod tests { use super::*; - + #[test] fn digit_count_base_10() { assert_eq!(digit_count(0, 10), 1); @@ -31,4 +31,4 @@ mod tests { assert_eq!(digit_count(1000000, 10), 7); assert_eq!(digit_count(9999999, 10), 7); } -} \ No newline at end of file +}