Updating to use the new Ropey.
This is mainly just for kicks and giggles, since it really won't work quite properly anyway, due to the lack of grapheme indexing. The next real thing is to... well, start from scratch.
This commit is contained in:
parent
8d146a595e
commit
9c302620e9
13
Cargo.lock
generated
13
Cargo.lock
generated
|
@ -4,7 +4,7 @@ version = "0.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"docopt 0.6.86 (registry+https://github.com/rust-lang/crates.io-index)",
|
"docopt 0.6.86 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
|
"encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"ropey 0.3.0",
|
"ropey 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"rustbox 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rustbox 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"unicode-segmentation 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"unicode-segmentation 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -253,8 +253,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ropey"
|
name = "ropey"
|
||||||
version = "0.3.0"
|
version = "0.5.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"smallvec 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"unicode-segmentation 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"unicode-segmentation 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -275,6 +277,11 @@ name = "rustc-serialize"
|
||||||
version = "0.3.24"
|
version = "0.3.24"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smallvec"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
|
@ -370,8 +377,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
"checksum redox_syscall 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)" = "8dde11f18c108289bef24469638a04dce49da56084f2d50618b226e47eb04509"
|
"checksum redox_syscall 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)" = "8dde11f18c108289bef24469638a04dce49da56084f2d50618b226e47eb04509"
|
||||||
"checksum regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)" = "4fd4ace6a8cf7860714a2c2280d6c1f7e6a413486c13298bbc86fd3da019402f"
|
"checksum regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)" = "4fd4ace6a8cf7860714a2c2280d6c1f7e6a413486c13298bbc86fd3da019402f"
|
||||||
"checksum regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957"
|
"checksum regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957"
|
||||||
|
"checksum ropey 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6eb4b74f76bc72338d8aebb193e9575398703552530e329dae718e38e4bf2ca0"
|
||||||
"checksum rustbox 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "67db7a8f30d0cadb67687a8c25ee32aaa57bf9d6497fd0fc92584b267bad975a"
|
"checksum rustbox 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "67db7a8f30d0cadb67687a8c25ee32aaa57bf9d6497fd0fc92584b267bad975a"
|
||||||
"checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
|
"checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
|
||||||
|
"checksum smallvec 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44db0ecb22921ef790d17ae13a3f6d15784183ff5f2a01aa32098c7498d2b4b9"
|
||||||
"checksum strsim 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "67f84c44fbb2f91db7fef94554e6b2ac05909c9c0b0bc23bb98d3a1aebfe7f7c"
|
"checksum strsim 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "67f84c44fbb2f91db7fef94554e6b2ac05909c9c0b0bc23bb98d3a1aebfe7f7c"
|
||||||
"checksum tempfile 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "11ce2fe9db64b842314052e2421ac61a73ce41b898dc8e3750398b219c5fc1e0"
|
"checksum tempfile 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "11ce2fe9db64b842314052e2421ac61a73ce41b898dc8e3750398b219c5fc1e0"
|
||||||
"checksum termbox-sys 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "e32daa27881ea4b2ef36e4972d6cdefb241c19ac48d318381b688d1a631b8760"
|
"checksum termbox-sys 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "e32daa27881ea4b2ef36e4972d6cdefb241c19ac48d318381b688d1a631b8760"
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
[workspace]
|
|
||||||
members = [
|
|
||||||
"sub_crates/ropey"
|
|
||||||
]
|
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "Led"
|
name = "Led"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
|
@ -14,12 +9,10 @@ name = "led"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
ropey = "0.5"
|
||||||
rustc-serialize = "0.3.0"
|
rustc-serialize = "0.3.0"
|
||||||
unicode-segmentation = "1.2"
|
unicode-segmentation = "1.2"
|
||||||
unicode-width = "0.1"
|
unicode-width = "0.1"
|
||||||
docopt = "0.6"
|
docopt = "0.6"
|
||||||
encoding = "0.2"
|
encoding = "0.2"
|
||||||
rustbox = "0.8"
|
rustbox = "0.8"
|
||||||
|
|
||||||
[dependencies.ropey]
|
|
||||||
path = "sub_crates/ropey"
|
|
|
@ -1,20 +1,18 @@
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use std::mem;
|
|
||||||
use std::cmp::min;
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::{BufReader, BufWriter, Read, Write};
|
use std::io::{BufReader, BufWriter, Read, Write};
|
||||||
|
|
||||||
use ropey::{Rope, RopeSlice, RopeGraphemeIter, RopeLineIter};
|
use ropey;
|
||||||
|
use ropey::{Rope, RopeSlice};
|
||||||
use self::undo_stack::UndoStack;
|
use self::undo_stack::UndoStack;
|
||||||
use self::undo_stack::Operation::*;
|
use self::undo_stack::Operation::*;
|
||||||
use string_utils::grapheme_count;
|
use string_utils::char_count;
|
||||||
|
|
||||||
mod undo_stack;
|
mod undo_stack;
|
||||||
|
|
||||||
|
|
||||||
// =============================================================
|
// =============================================================
|
||||||
// Buffer
|
// Buffer
|
||||||
// =============================================================
|
// =============================================================
|
||||||
|
@ -26,8 +24,6 @@ pub struct Buffer {
|
||||||
undo_stack: UndoStack,
|
undo_stack: UndoStack,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
impl Buffer {
|
impl Buffer {
|
||||||
pub fn new() -> Buffer {
|
pub fn new() -> Buffer {
|
||||||
Buffer {
|
Buffer {
|
||||||
|
@ -37,7 +33,6 @@ impl Buffer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn new_from_str(s: &str) -> Buffer {
|
pub fn new_from_str(s: &str) -> Buffer {
|
||||||
Buffer {
|
Buffer {
|
||||||
text: Rope::from_str(s),
|
text: Rope::from_str(s),
|
||||||
|
@ -46,7 +41,6 @@ impl Buffer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn new_from_file(path: &Path) -> io::Result<Buffer> {
|
pub fn new_from_file(path: &Path) -> io::Result<Buffer> {
|
||||||
let mut f = BufReader::new(try!(File::open(path)));
|
let mut f = BufReader::new(try!(File::open(path)));
|
||||||
let mut string = String::new();
|
let mut string = String::new();
|
||||||
|
@ -61,127 +55,82 @@ impl Buffer {
|
||||||
return Ok(buf);
|
return Ok(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn save_to_file(&self, path: &Path) -> io::Result<()> {
|
pub fn save_to_file(&self, path: &Path) -> io::Result<()> {
|
||||||
let mut f = BufWriter::new(try!(File::create(path)));
|
let mut f = BufWriter::new(try!(File::create(path)));
|
||||||
|
|
||||||
for c in self.text.chunk_iter() {
|
for c in self.text.chunks() {
|
||||||
let _ = f.write(c.as_bytes());
|
let _ = f.write(c.as_bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// Functions for getting information about the buffer.
|
// Functions for getting information about the buffer.
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
pub fn char_count(&self) -> usize {
|
pub fn char_count(&self) -> usize {
|
||||||
self.text.char_count()
|
self.text.len_chars()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn grapheme_count(&self) -> usize {
|
pub fn grapheme_count(&self) -> usize {
|
||||||
self.text.grapheme_count()
|
// TODO: be correct
|
||||||
|
self.text.len_chars()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn line_count(&self) -> usize {
|
pub fn line_count(&self) -> usize {
|
||||||
self.text.line_ending_count() + 1
|
self.text.len_lines()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// Editing operations
|
// Editing operations
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
/// Insert 'text' at grapheme position 'pos'.
|
/// Insert 'text' at grapheme position 'pos'.
|
||||||
pub fn insert_text(&mut self, text: &str, pos: usize) {
|
pub fn insert_text(&mut self, text: &str, pos: usize) {
|
||||||
let cpos = self.text.grapheme_index_to_char_index(pos);
|
self.text.insert(pos, text);
|
||||||
self._insert_text(text, cpos);
|
|
||||||
|
|
||||||
self.undo_stack.push(InsertText(text.to_string(), cpos));
|
self.undo_stack.push(InsertText(text.to_string(), pos));
|
||||||
}
|
}
|
||||||
|
|
||||||
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'.
|
/// Remove the text before grapheme position 'pos' of length 'len'.
|
||||||
pub fn remove_text_before(&mut self, pos: usize, len: usize) {
|
pub fn remove_text_before(&mut self, pos: usize, len: usize) {
|
||||||
if pos >= len {
|
if pos >= len {
|
||||||
let cpos_a = self.text.grapheme_index_to_char_index(pos);
|
let removed_text = self.text.slice(pos - len, pos).to_string();
|
||||||
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);
|
self.text.remove(pos - len, pos);
|
||||||
|
|
||||||
// Push operation to the undo stack
|
// Push operation to the undo stack
|
||||||
self.undo_stack.push(RemoveTextBefore(removed_text, cpos_b));
|
self.undo_stack
|
||||||
|
.push(RemoveTextBefore(removed_text, pos - len));
|
||||||
} else {
|
} else {
|
||||||
panic!("Buffer::remove_text_before(): attempt to remove text before beginning of \
|
panic!(
|
||||||
buffer.");
|
"Buffer::remove_text_before(): attempt to remove text before beginning of \
|
||||||
|
buffer."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove the text after grapheme position 'pos' of length 'len'.
|
/// Remove the text after grapheme position 'pos' of length 'len'.
|
||||||
pub fn remove_text_after(&mut self, pos: usize, len: usize) {
|
pub fn remove_text_after(&mut self, pos: usize, len: usize) {
|
||||||
let cpos_a = self.text.grapheme_index_to_char_index(pos);
|
let removed_text = self.text.slice(pos, pos + len).to_string();
|
||||||
let cpos_b = self.text.grapheme_index_to_char_index(pos + len);
|
|
||||||
|
|
||||||
let removed_text = self.string_from_range(cpos_a, cpos_b);
|
self.text.remove(pos, pos + len);
|
||||||
|
|
||||||
self._remove_text(cpos_a, cpos_b);
|
|
||||||
|
|
||||||
// Push operation to the undo stack
|
// Push operation to the undo stack
|
||||||
self.undo_stack.push(RemoveTextAfter(removed_text, cpos_a));
|
self.undo_stack.push(RemoveTextAfter(removed_text, pos));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _remove_text(&mut self, pos_a: usize, pos_b: usize) {
|
|
||||||
// Nothing to do
|
|
||||||
if pos_a == pos_b {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Bounds error
|
|
||||||
else if pos_a > pos_b {
|
|
||||||
panic!("Buffer::_remove_text(): pos_a must be less than or equal to pos_b.");
|
|
||||||
}
|
|
||||||
// Bounds error
|
|
||||||
else if pos_b > self.char_count() {
|
|
||||||
panic!("Buffer::_remove_text(): attempt to remove text past the end of buffer.");
|
|
||||||
}
|
|
||||||
// Complete removal of all text
|
|
||||||
else if pos_a == 0 && pos_b == self.text.char_count() {
|
|
||||||
let mut temp_node = Rope::new();
|
|
||||||
mem::swap(&mut (self.text), &mut temp_node);
|
|
||||||
}
|
|
||||||
// All other cases
|
|
||||||
else {
|
|
||||||
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.
|
/// 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
|
/// Note that pos_to is the desired index that the text will start at
|
||||||
/// _after_ the operation, not the index before the operation. This is a
|
/// _after_ the operation, not the index before the operation. This is a
|
||||||
/// subtle but important distinction.
|
/// subtle but important distinction.
|
||||||
pub fn move_text(&mut self, pos_a: usize, pos_b: usize, pos_to: usize) {
|
pub fn move_text(&mut self, pos_a: usize, pos_b: usize, pos_to: usize) {
|
||||||
let cpos_a = self.text.grapheme_index_to_char_index(pos_a);
|
self._move_text(pos_a, pos_b, pos_to);
|
||||||
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
|
// Push operation to the undo stack
|
||||||
self.undo_stack.push(MoveText(cpos_a, cpos_b, cpos_to));
|
self.undo_stack.push(MoveText(pos_a, pos_b, pos_to));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _move_text(&mut self, pos_a: usize, pos_b: usize, pos_to: usize) {
|
fn _move_text(&mut self, pos_a: usize, pos_b: usize, pos_to: usize) {
|
||||||
|
@ -194,28 +143,27 @@ impl Buffer {
|
||||||
panic!("Buffer::_move_text(): pos_a must be less than or equal to pos_b.");
|
panic!("Buffer::_move_text(): pos_a must be less than or equal to pos_b.");
|
||||||
}
|
}
|
||||||
// Bounds error
|
// Bounds error
|
||||||
else if pos_b > self.grapheme_count() {
|
else if pos_b > self.text.len_chars() {
|
||||||
panic!("Buffer::_move_text(): specified text range is beyond end of buffer.");
|
panic!("Buffer::_move_text(): specified text range is beyond end of buffer.");
|
||||||
}
|
}
|
||||||
// Bounds error
|
// Bounds error
|
||||||
else if pos_to > (self.grapheme_count() - (pos_b - pos_a)) {
|
else if pos_to > (self.text.len_chars() - (pos_b - pos_a)) {
|
||||||
panic!("Buffer::_move_text(): specified text destination is beyond end of buffer.");
|
panic!("Buffer::_move_text(): specified text destination is beyond end of buffer.");
|
||||||
}
|
}
|
||||||
// Nothing to do, because entire text specified
|
// Nothing to do, because entire text specified
|
||||||
else if pos_a == 0 && pos_b == self.char_count() {
|
else if pos_a == 0 && pos_b == self.text.len_chars() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// All other cases
|
// All other cases
|
||||||
else {
|
else {
|
||||||
// TODO: a more efficient implementation that directly
|
// TODO: a more efficient implementation that directly
|
||||||
// manipulates the node tree.
|
// manipulates the node tree.
|
||||||
let s = self.string_from_range(pos_a, pos_b);
|
let s = self.text.slice(pos_a, pos_b).to_string();
|
||||||
self._remove_text(pos_a, pos_b);
|
self.text.remove(pos_a, pos_b);
|
||||||
self._insert_text(&s[..], pos_to);
|
self.text.insert(pos_to, &s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Removes the lines in line indices [line_a, line_b).
|
/// Removes the lines in line indices [line_a, line_b).
|
||||||
/// TODO: undo
|
/// TODO: undo
|
||||||
pub fn remove_lines(&mut self, line_a: usize, line_b: usize) {
|
pub fn remove_lines(&mut self, line_a: usize, line_b: usize) {
|
||||||
|
@ -231,36 +179,15 @@ impl Buffer {
|
||||||
else if line_b > self.line_count() {
|
else if line_b > self.line_count() {
|
||||||
panic!("Buffer::remove_lines(): attempt to remove lines past the last line of text.");
|
panic!("Buffer::remove_lines(): attempt to remove lines past the last line of text.");
|
||||||
}
|
}
|
||||||
// Complete removal of all lines
|
|
||||||
else if line_a == 0 && line_b == (self.text.line_ending_count() + 1) {
|
|
||||||
let mut temp_node = Rope::new();
|
|
||||||
mem::swap(&mut (self.text), &mut temp_node);
|
|
||||||
}
|
|
||||||
// All other cases
|
// All other cases
|
||||||
else {
|
else {
|
||||||
let a = if line_a > 0 {
|
let a = self.text.line_to_char(line_a);
|
||||||
self.text.line_index_to_char_index(line_a) - 1
|
let b = self.text.line_to_char(line_b);
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
|
|
||||||
let b = if line_b < self.line_count() {
|
self.text.remove(a, b);
|
||||||
if line_a > 0 {
|
|
||||||
self.text.line_index_to_char_index(line_b) - 1
|
|
||||||
} else {
|
|
||||||
self.text.line_index_to_char_index(line_b)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.text.char_count()
|
|
||||||
};
|
|
||||||
|
|
||||||
self.text.remove_text_between_char_indices(a, b);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// Undo/redo functionality
|
// Undo/redo functionality
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
@ -271,19 +198,19 @@ impl Buffer {
|
||||||
if let Some(op) = self.undo_stack.prev() {
|
if let Some(op) = self.undo_stack.prev() {
|
||||||
match op {
|
match op {
|
||||||
InsertText(ref s, p) => {
|
InsertText(ref s, p) => {
|
||||||
let size = grapheme_count(&s[..]);
|
let size = char_count(s);
|
||||||
self._remove_text(p, p + size);
|
self.text.remove(p, p + size);
|
||||||
return Some(p);
|
return Some(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
RemoveTextBefore(ref s, p) => {
|
RemoveTextBefore(ref s, p) => {
|
||||||
let size = grapheme_count(&s[..]);
|
let size = char_count(s);
|
||||||
self._insert_text(&s[..], p);
|
self.text.insert(p, s);
|
||||||
return Some(p + size);
|
return Some(p + size);
|
||||||
}
|
}
|
||||||
|
|
||||||
RemoveTextAfter(ref s, p) => {
|
RemoveTextAfter(ref s, p) => {
|
||||||
self._insert_text(&s[..], p);
|
self.text.insert(p, s);
|
||||||
return Some(p);
|
return Some(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -302,21 +229,20 @@ impl Buffer {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Redoes the last undone operation, and returns a cursor position that
|
/// Redoes the last undone operation, and returns a cursor position that
|
||||||
/// the cursor should jump to, if any.
|
/// the cursor should jump to, if any.
|
||||||
pub fn redo(&mut self) -> Option<usize> {
|
pub fn redo(&mut self) -> Option<usize> {
|
||||||
if let Some(op) = self.undo_stack.next() {
|
if let Some(op) = self.undo_stack.next() {
|
||||||
match op {
|
match op {
|
||||||
InsertText(ref s, p) => {
|
InsertText(ref s, p) => {
|
||||||
let size = grapheme_count(&s[..]);
|
let size = char_count(s);
|
||||||
self._insert_text(&s[..], p);
|
self.text.insert(p, s);
|
||||||
return Some(p + size);
|
return Some(p + size);
|
||||||
}
|
}
|
||||||
|
|
||||||
RemoveTextBefore(ref s, p) | RemoveTextAfter(ref s, p) => {
|
RemoveTextBefore(ref s, p) | RemoveTextAfter(ref s, p) => {
|
||||||
let size = grapheme_count(&s[..]);
|
let size = char_count(s);
|
||||||
self._remove_text(p, p + size);
|
self.text.remove(p, p + size);
|
||||||
return Some(p);
|
return Some(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -334,37 +260,23 @@ impl Buffer {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// Position conversions
|
// Position conversions
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
/// Converts a grapheme index into a line number and grapheme-column
|
/// Converts a char index into a line number and char-column
|
||||||
/// number.
|
/// number.
|
||||||
///
|
///
|
||||||
/// If the index is off the end of the text, returns the line and column
|
/// If the index is off the end of the text, returns the line and column
|
||||||
/// number of the last valid text position.
|
/// number of the last valid text position.
|
||||||
pub fn index_to_line_col(&self, pos: usize) -> (usize, usize) {
|
pub fn index_to_line_col(&self, pos: usize) -> (usize, usize) {
|
||||||
// Convert to char index
|
let line = self.text.char_to_line(pos);
|
||||||
let cpos = if pos < self.text.grapheme_count() {
|
let line_pos = self.text.line_to_char(line);
|
||||||
self.text.grapheme_index_to_char_index(pos)
|
|
||||||
} else {
|
|
||||||
self.text.char_count()
|
|
||||||
};
|
|
||||||
|
|
||||||
let line = self.text.char_index_to_line_index(cpos);
|
return (line, pos - line_pos);
|
||||||
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 char-column number into a char
|
||||||
/// Converts a line number and grapheme-column number into a grapheme
|
|
||||||
/// index.
|
/// index.
|
||||||
///
|
///
|
||||||
/// If the column number given is beyond the end of the line, returns the
|
/// If the column number given is beyond the end of the line, returns the
|
||||||
|
@ -372,112 +284,63 @@ impl Buffer {
|
||||||
/// beyond the end of the buffer, returns the index of the buffer's last
|
/// beyond the end of the buffer, returns the index of the buffer's last
|
||||||
/// valid position.
|
/// valid position.
|
||||||
pub fn line_col_to_index(&self, pos: (usize, usize)) -> usize {
|
pub fn line_col_to_index(&self, pos: (usize, usize)) -> usize {
|
||||||
if pos.0 <= self.text.line_ending_count() {
|
if pos.0 < self.text.len_lines() {
|
||||||
let temp1 = self.text.line_index_to_char_index(pos.0);
|
let l_begin_pos = self.text.line_to_char(pos.0);
|
||||||
let l_begin_pos = self.text.char_index_to_grapheme_index(temp1);
|
return (l_begin_pos + pos.1).min(self.text.len_chars());
|
||||||
|
|
||||||
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 {
|
|
||||||
self.text.grapheme_count()
|
|
||||||
};
|
|
||||||
|
|
||||||
return min(l_begin_pos + pos.1, l_end_pos);
|
|
||||||
} else {
|
} else {
|
||||||
return self.text.grapheme_count();
|
return self.text.len_chars();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// Text reading functions
|
// Text reading functions
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
pub fn get_grapheme<'a>(&'a self, index: usize) -> &'a str {
|
pub fn get_grapheme<'a>(&'a self, index: usize) -> &'a str {
|
||||||
if index >= self.grapheme_count() {
|
self.text
|
||||||
panic!("Buffer::get_grapheme(): index past last grapheme.");
|
.slice(index, index + 1)
|
||||||
} else {
|
.graphemes()
|
||||||
return self.text.grapheme_at_index(index);
|
.nth(0)
|
||||||
}
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn get_line<'a>(&'a self, index: usize) -> RopeSlice<'a> {
|
pub fn get_line<'a>(&'a self, index: usize) -> RopeSlice<'a> {
|
||||||
if index >= self.line_count() {
|
self.text.line(index)
|
||||||
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 {
|
|
||||||
self.text.char_count()
|
|
||||||
};
|
|
||||||
|
|
||||||
return self.text.slice(a, b);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Creates a String from the buffer text in grapheme range [pos_a, posb).
|
/// 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 {
|
fn string_from_range(&self, pos_a: usize, pos_b: usize) -> String {
|
||||||
// Bounds checks
|
self.text.slice(pos_a, pos_b).to_string()
|
||||||
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() {
|
|
||||||
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
|
// Iterator creators
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
/// Creates an iterator at the first character
|
/// Creates an iterator at the first character
|
||||||
pub fn grapheme_iter<'a>(&'a self) -> RopeGraphemeIter<'a> {
|
pub fn grapheme_iter<'a>(&'a self) -> ropey::iter::Graphemes<'a> {
|
||||||
self.text.grapheme_iter()
|
self.text.graphemes()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Creates an iterator starting at the specified grapheme index.
|
/// Creates an iterator starting at the specified grapheme index.
|
||||||
/// If the index is past the end of the text, then the iterator will
|
/// If the index is past the end of the text, then the iterator will
|
||||||
/// return None on next().
|
/// return None on next().
|
||||||
pub fn grapheme_iter_at_index<'a>(&'a self, index: usize) -> RopeGraphemeIter<'a> {
|
pub fn grapheme_iter_at_index<'a>(&'a self, index: usize) -> ropey::iter::Graphemes<'a> {
|
||||||
self.text.grapheme_iter_at_index(index)
|
let len = self.text.len_chars();
|
||||||
|
self.text.slice(index, len).graphemes()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn line_iter<'a>(&'a self) -> ropey::iter::Lines<'a> {
|
||||||
pub fn line_iter<'a>(&'a self) -> RopeLineIter<'a> {
|
self.text.lines()
|
||||||
self.text.line_iter()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn line_iter_at_index<'a>(&'a self, line_idx: usize) -> ropey::iter::Lines<'a> {
|
||||||
pub fn line_iter_at_index<'a>(&'a self, index: usize) -> RopeLineIter<'a> {
|
let start = self.text.line_to_char(line_idx);
|
||||||
self.text.line_iter_at_index(index)
|
let len = self.text.len_chars();
|
||||||
|
self.text.slice(start, len).lines()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ================================================================
|
// ================================================================
|
||||||
// TESTS
|
// TESTS
|
||||||
// ================================================================
|
// ================================================================
|
||||||
|
@ -509,7 +372,6 @@ mod tests {
|
||||||
assert!(None == iter.next());
|
assert!(None == iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn insert_text_with_newlines() {
|
fn insert_text_with_newlines() {
|
||||||
let mut buf = Buffer::new();
|
let mut buf = Buffer::new();
|
||||||
|
@ -534,7 +396,6 @@ mod tests {
|
||||||
assert!(None == iter.next());
|
assert!(None == iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn insert_text_in_non_empty_buffer_1() {
|
fn insert_text_in_non_empty_buffer_1() {
|
||||||
let mut buf = Buffer::new();
|
let mut buf = Buffer::new();
|
||||||
|
@ -566,7 +427,6 @@ mod tests {
|
||||||
assert!(None == iter.next());
|
assert!(None == iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn insert_text_in_non_empty_buffer_2() {
|
fn insert_text_in_non_empty_buffer_2() {
|
||||||
let mut buf = Buffer::new();
|
let mut buf = Buffer::new();
|
||||||
|
@ -598,7 +458,6 @@ mod tests {
|
||||||
assert!(None == iter.next());
|
assert!(None == iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn insert_text_in_non_empty_buffer_3() {
|
fn insert_text_in_non_empty_buffer_3() {
|
||||||
let mut buf = Buffer::new();
|
let mut buf = Buffer::new();
|
||||||
|
@ -629,7 +488,6 @@ mod tests {
|
||||||
assert!(None == iter.next());
|
assert!(None == iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn insert_text_in_non_empty_buffer_4() {
|
fn insert_text_in_non_empty_buffer_4() {
|
||||||
let mut buf = Buffer::new();
|
let mut buf = Buffer::new();
|
||||||
|
@ -660,7 +518,6 @@ mod tests {
|
||||||
assert!(None == iter.next());
|
assert!(None == iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn insert_text_in_non_empty_buffer_5() {
|
fn insert_text_in_non_empty_buffer_5() {
|
||||||
let mut buf = Buffer::new();
|
let mut buf = Buffer::new();
|
||||||
|
@ -692,7 +549,6 @@ mod tests {
|
||||||
assert!(None == iter.next());
|
assert!(None == iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn insert_text_in_non_empty_buffer_6() {
|
fn insert_text_in_non_empty_buffer_6() {
|
||||||
let mut buf = Buffer::new();
|
let mut buf = Buffer::new();
|
||||||
|
@ -724,7 +580,6 @@ mod tests {
|
||||||
assert!(None == iter.next());
|
assert!(None == iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn insert_text_in_non_empty_buffer_7() {
|
fn insert_text_in_non_empty_buffer_7() {
|
||||||
let mut buf = Buffer::new();
|
let mut buf = Buffer::new();
|
||||||
|
@ -760,7 +615,6 @@ mod tests {
|
||||||
assert!(None == iter.next());
|
assert!(None == iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn remove_text_1() {
|
fn remove_text_1() {
|
||||||
let mut buf = Buffer::new();
|
let mut buf = Buffer::new();
|
||||||
|
@ -804,7 +658,6 @@ mod tests {
|
||||||
assert!(None == iter.next());
|
assert!(None == iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn remove_text_2() {
|
fn remove_text_2() {
|
||||||
let mut buf = Buffer::new();
|
let mut buf = Buffer::new();
|
||||||
|
@ -839,7 +692,6 @@ mod tests {
|
||||||
assert!(None == iter.next());
|
assert!(None == iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn remove_text_3() {
|
fn remove_text_3() {
|
||||||
let mut buf = Buffer::new();
|
let mut buf = Buffer::new();
|
||||||
|
@ -874,7 +726,6 @@ mod tests {
|
||||||
assert!(None == iter.next());
|
assert!(None == iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn remove_text_4() {
|
fn remove_text_4() {
|
||||||
let mut buf = Buffer::new();
|
let mut buf = Buffer::new();
|
||||||
|
@ -915,7 +766,6 @@ mod tests {
|
||||||
assert!(None == iter.next());
|
assert!(None == iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn remove_text_5() {
|
fn remove_text_5() {
|
||||||
let mut buf = Buffer::new();
|
let mut buf = Buffer::new();
|
||||||
|
@ -950,7 +800,6 @@ mod tests {
|
||||||
assert!(None == iter.next());
|
assert!(None == iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn remove_text_6() {
|
fn remove_text_6() {
|
||||||
let mut buf = Buffer::new();
|
let mut buf = Buffer::new();
|
||||||
|
@ -971,7 +820,6 @@ mod tests {
|
||||||
assert!(None == iter.next());
|
assert!(None == iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn remove_text_7() {
|
fn remove_text_7() {
|
||||||
let mut buf = Buffer::new();
|
let mut buf = Buffer::new();
|
||||||
|
@ -994,7 +842,6 @@ mod tests {
|
||||||
assert!(None == iter.next());
|
assert!(None == iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn remove_text_8() {
|
fn remove_text_8() {
|
||||||
let mut buf = Buffer::new();
|
let mut buf = Buffer::new();
|
||||||
|
@ -1016,7 +863,6 @@ mod tests {
|
||||||
assert!(None == iter.next());
|
assert!(None == iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn remove_text_9() {
|
fn remove_text_9() {
|
||||||
let mut buf = Buffer::new();
|
let mut buf = Buffer::new();
|
||||||
|
@ -1042,7 +888,6 @@ mod tests {
|
||||||
assert!(None == iter.next());
|
assert!(None == iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn remove_text_10() {
|
fn remove_text_10() {
|
||||||
let mut buf = Buffer::new();
|
let mut buf = Buffer::new();
|
||||||
|
@ -1064,7 +909,6 @@ mod tests {
|
||||||
assert!(None == iter.next());
|
assert!(None == iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn remove_text_11() {
|
fn remove_text_11() {
|
||||||
let mut buf = Buffer::new();
|
let mut buf = Buffer::new();
|
||||||
|
@ -1091,7 +935,6 @@ mod tests {
|
||||||
assert!(None == iter.next());
|
assert!(None == iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn move_text_1() {
|
fn move_text_1() {
|
||||||
let mut buf = Buffer::new();
|
let mut buf = Buffer::new();
|
||||||
|
@ -1136,7 +979,6 @@ mod tests {
|
||||||
assert!(None == iter.next());
|
assert!(None == iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn move_text_2() {
|
fn move_text_2() {
|
||||||
let mut buf = Buffer::new();
|
let mut buf = Buffer::new();
|
||||||
|
@ -1181,7 +1023,6 @@ mod tests {
|
||||||
assert!(None == iter.next());
|
assert!(None == iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn move_text_3() {
|
fn move_text_3() {
|
||||||
let mut buf = Buffer::new();
|
let mut buf = Buffer::new();
|
||||||
|
@ -1226,7 +1067,6 @@ mod tests {
|
||||||
assert!(None == iter.next());
|
assert!(None == iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn move_text_4() {
|
fn move_text_4() {
|
||||||
let mut buf = Buffer::new();
|
let mut buf = Buffer::new();
|
||||||
|
@ -1271,7 +1111,6 @@ mod tests {
|
||||||
assert!(None == iter.next());
|
assert!(None == iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn move_text_5() {
|
fn move_text_5() {
|
||||||
let mut buf = Buffer::new();
|
let mut buf = Buffer::new();
|
||||||
|
@ -1316,7 +1155,6 @@ mod tests {
|
||||||
assert!(None == iter.next());
|
assert!(None == iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn remove_lines_1() {
|
fn remove_lines_1() {
|
||||||
let mut buf = Buffer::new();
|
let mut buf = Buffer::new();
|
||||||
|
@ -1347,7 +1185,6 @@ mod tests {
|
||||||
assert!(None == iter.next());
|
assert!(None == iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn remove_lines_2() {
|
fn remove_lines_2() {
|
||||||
let mut buf = Buffer::new();
|
let mut buf = Buffer::new();
|
||||||
|
@ -1378,7 +1215,6 @@ mod tests {
|
||||||
assert!(None == iter.next());
|
assert!(None == iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn remove_lines_3() {
|
fn remove_lines_3() {
|
||||||
let mut buf = Buffer::new();
|
let mut buf = Buffer::new();
|
||||||
|
@ -1411,7 +1247,6 @@ mod tests {
|
||||||
assert!(None == iter.next());
|
assert!(None == iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn remove_lines_4() {
|
fn remove_lines_4() {
|
||||||
let mut buf = Buffer::new();
|
let mut buf = Buffer::new();
|
||||||
|
@ -1444,7 +1279,6 @@ mod tests {
|
||||||
assert!(None == iter.next());
|
assert!(None == iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn remove_lines_5() {
|
fn remove_lines_5() {
|
||||||
let mut buf = Buffer::new();
|
let mut buf = Buffer::new();
|
||||||
|
@ -1462,7 +1296,6 @@ mod tests {
|
||||||
assert!(None == iter.next());
|
assert!(None == iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn remove_lines_6() {
|
fn remove_lines_6() {
|
||||||
let mut buf = Buffer::new();
|
let mut buf = Buffer::new();
|
||||||
|
@ -1480,7 +1313,6 @@ mod tests {
|
||||||
assert!(None == iter.next());
|
assert!(None == iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn line_col_to_index_1() {
|
fn line_col_to_index_1() {
|
||||||
let mut buf = Buffer::new();
|
let mut buf = Buffer::new();
|
||||||
|
@ -1491,7 +1323,6 @@ mod tests {
|
||||||
assert!(pos == 12);
|
assert!(pos == 12);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn line_col_to_index_2() {
|
fn line_col_to_index_2() {
|
||||||
let mut buf = Buffer::new();
|
let mut buf = Buffer::new();
|
||||||
|
@ -1512,7 +1343,6 @@ mod tests {
|
||||||
assert!(pos == 29);
|
assert!(pos == 29);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn line_col_to_index_4() {
|
fn line_col_to_index_4() {
|
||||||
let mut buf = Buffer::new();
|
let mut buf = Buffer::new();
|
||||||
|
@ -1530,7 +1360,6 @@ mod tests {
|
||||||
assert_eq!(buf.line_col_to_index((2, 1)), 13);
|
assert_eq!(buf.line_col_to_index((2, 1)), 13);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn index_to_line_col_1() {
|
fn index_to_line_col_1() {
|
||||||
let mut buf = Buffer::new();
|
let mut buf = Buffer::new();
|
||||||
|
@ -1541,7 +1370,6 @@ mod tests {
|
||||||
assert!(pos == (1, 2));
|
assert!(pos == (1, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn index_to_line_col_2() {
|
fn index_to_line_col_2() {
|
||||||
let mut buf = Buffer::new();
|
let mut buf = Buffer::new();
|
||||||
|
@ -1565,7 +1393,6 @@ mod tests {
|
||||||
assert_eq!(buf.index_to_line_col(14), (2, 0));
|
assert_eq!(buf.index_to_line_col(14), (2, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn string_from_range_1() {
|
fn string_from_range_1() {
|
||||||
let mut buf = Buffer::new();
|
let mut buf = Buffer::new();
|
||||||
|
@ -1576,7 +1403,6 @@ mod tests {
|
||||||
assert!(&s[..] == "i\nthere\npeo");
|
assert!(&s[..] == "i\nthere\npeo");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn string_from_range_2() {
|
fn string_from_range_2() {
|
||||||
let mut buf = Buffer::new();
|
let mut buf = Buffer::new();
|
||||||
|
@ -1587,7 +1413,6 @@ mod tests {
|
||||||
assert!(&s[..] == "Hi\nthere\npeople\nof\nthe\nworld!");
|
assert!(&s[..] == "Hi\nthere\npeople\nof\nthe\nworld!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn grapheme_iter_at_index_1() {
|
fn grapheme_iter_at_index_1() {
|
||||||
let mut buf = Buffer::new();
|
let mut buf = Buffer::new();
|
||||||
|
@ -1611,7 +1436,6 @@ mod tests {
|
||||||
assert!(None == iter.next());
|
assert!(None == iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn grapheme_iter_at_index_2() {
|
fn grapheme_iter_at_index_2() {
|
||||||
let mut buf = Buffer::new();
|
let mut buf = Buffer::new();
|
||||||
|
@ -1622,5 +1446,4 @@ mod tests {
|
||||||
assert!(None == iter.next());
|
assert!(None == iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use std::collections::LinkedList;
|
use std::collections::LinkedList;
|
||||||
|
|
||||||
|
|
||||||
/// A text editing operation
|
/// A text editing operation
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum Operation {
|
pub enum Operation {
|
||||||
|
@ -11,7 +10,6 @@ pub enum Operation {
|
||||||
CompositeOp(Vec<Operation>),
|
CompositeOp(Vec<Operation>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// An undo/redo stack of text editing operations
|
/// An undo/redo stack of text editing operations
|
||||||
pub struct UndoStack {
|
pub struct UndoStack {
|
||||||
stack_a: LinkedList<Operation>,
|
stack_a: LinkedList<Operation>,
|
||||||
|
@ -26,13 +24,11 @@ impl UndoStack {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn push(&mut self, op: Operation) {
|
pub fn push(&mut self, op: Operation) {
|
||||||
self.stack_a.push_back(op);
|
self.stack_a.push_back(op);
|
||||||
self.stack_b.clear();
|
self.stack_b.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn prev(&mut self) -> Option<Operation> {
|
pub fn prev(&mut self) -> Option<Operation> {
|
||||||
if let Some(op) = self.stack_a.pop_back() {
|
if let Some(op) = self.stack_a.pop_back() {
|
||||||
self.stack_b.push_back(op.clone());
|
self.stack_b.push_back(op.clone());
|
||||||
|
@ -42,7 +38,6 @@ impl UndoStack {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn next(&mut self) -> Option<Operation> {
|
pub fn next(&mut self) -> Option<Operation> {
|
||||||
if let Some(op) = self.stack_b.pop_back() {
|
if let Some(op) = self.stack_b.pop_back() {
|
||||||
self.stack_a.push_back(op.clone());
|
self.stack_a.push_back(op.clone());
|
||||||
|
|
|
@ -16,7 +16,7 @@ use formatter::LineFormatter;
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct Cursor {
|
pub struct Cursor {
|
||||||
pub range: (usize, usize), // start, end
|
pub range: (usize, usize), // start, end
|
||||||
pub vis_start: usize, // start
|
pub vis_start: usize, // start
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cursor {
|
impl Cursor {
|
||||||
|
@ -32,17 +32,17 @@ impl Cursor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// A collection of cursors, managed to always be in a consistent
|
/// A collection of cursors, managed to always be in a consistent
|
||||||
/// state for multi-cursor editing.
|
/// state for multi-cursor editing.
|
||||||
pub struct CursorSet {
|
pub struct CursorSet {
|
||||||
cursors: Vec<Cursor>,
|
cursors: Vec<Cursor>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl CursorSet {
|
impl CursorSet {
|
||||||
pub fn new() -> CursorSet {
|
pub fn new() -> CursorSet {
|
||||||
CursorSet { cursors: vec![Cursor::new()] }
|
CursorSet {
|
||||||
|
cursors: vec![Cursor::new()],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_cursor(&mut self, cursor: Cursor) {
|
pub fn add_cursor(&mut self, cursor: Cursor) {
|
||||||
|
@ -87,7 +87,6 @@ impl CursorSet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl Index<usize> for CursorSet {
|
impl Index<usize> for CursorSet {
|
||||||
type Output = Cursor;
|
type Output = Cursor;
|
||||||
|
|
||||||
|
@ -96,7 +95,6 @@ impl Index<usize> for CursorSet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl IndexMut<usize> for CursorSet {
|
impl IndexMut<usize> for CursorSet {
|
||||||
fn index_mut<'a>(&'a mut self, _index: usize) -> &'a mut Cursor {
|
fn index_mut<'a>(&'a mut self, _index: usize) -> &'a mut Cursor {
|
||||||
&mut (self.cursors[_index])
|
&mut (self.cursors[_index])
|
||||||
|
|
|
@ -4,14 +4,13 @@ use buffer::Buffer;
|
||||||
use formatter::LineFormatter;
|
use formatter::LineFormatter;
|
||||||
use formatter::RoundingBehavior::*;
|
use formatter::RoundingBehavior::*;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::cmp::{min, max};
|
use std::cmp::{max, min};
|
||||||
use string_utils::{grapheme_count, str_to_line_ending, LineEnding};
|
use string_utils::{grapheme_count, str_to_line_ending, LineEnding};
|
||||||
use utils::digit_count;
|
use utils::digit_count;
|
||||||
use self::cursor::CursorSet;
|
use self::cursor::CursorSet;
|
||||||
|
|
||||||
mod cursor;
|
mod cursor;
|
||||||
|
|
||||||
|
|
||||||
pub struct Editor<T: LineFormatter> {
|
pub struct Editor<T: LineFormatter> {
|
||||||
pub buffer: Buffer,
|
pub buffer: Buffer,
|
||||||
pub formatter: T,
|
pub formatter: T,
|
||||||
|
@ -33,7 +32,6 @@ pub struct Editor<T: LineFormatter> {
|
||||||
pub cursors: CursorSet,
|
pub cursors: CursorSet,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl<T: LineFormatter> Editor<T> {
|
impl<T: LineFormatter> Editor<T> {
|
||||||
/// Create a new blank editor
|
/// Create a new blank editor
|
||||||
pub fn new(formatter: T) -> Editor<T> {
|
pub fn new(formatter: T) -> Editor<T> {
|
||||||
|
@ -52,7 +50,6 @@ impl<T: LineFormatter> Editor<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn new_from_file(formatter: T, path: &Path) -> Editor<T> {
|
pub fn new_from_file(formatter: T, path: &Path) -> Editor<T> {
|
||||||
let buf = match Buffer::new_from_file(path) {
|
let buf = match Buffer::new_from_file(path) {
|
||||||
Ok(b) => b,
|
Ok(b) => b,
|
||||||
|
@ -87,7 +84,6 @@ impl<T: LineFormatter> Editor<T> {
|
||||||
return ed;
|
return ed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn save_if_dirty(&mut self) {
|
pub fn save_if_dirty(&mut self) {
|
||||||
if self.dirty && self.file_path != PathBuf::new() {
|
if self.dirty && self.file_path != PathBuf::new() {
|
||||||
let _ = self.buffer.save_to_file(&self.file_path);
|
let _ = self.buffer.save_to_file(&self.file_path);
|
||||||
|
@ -95,7 +91,6 @@ impl<T: LineFormatter> Editor<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn auto_detect_line_ending(&mut self) {
|
pub fn auto_detect_line_ending(&mut self) {
|
||||||
let mut line_ending_histogram: [usize; 8] = [0, 0, 0, 0, 0, 0, 0, 0];
|
let mut line_ending_histogram: [usize; 8] = [0, 0, 0, 0, 0, 0, 0, 0];
|
||||||
|
|
||||||
|
@ -103,8 +98,8 @@ impl<T: LineFormatter> Editor<T> {
|
||||||
let mut line_i: usize = 0;
|
let mut line_i: usize = 0;
|
||||||
for line in self.buffer.line_iter() {
|
for line in self.buffer.line_iter() {
|
||||||
// Get the line ending
|
// Get the line ending
|
||||||
let ending = if line.grapheme_count() > 0 {
|
let ending = if line.len_chars() > 0 {
|
||||||
let g = line.grapheme_at_index(line.grapheme_count() - 1);
|
let g = line.slice(line.len_chars() - 1, line.len_chars()).graphemes().nth(0).unwrap();
|
||||||
str_to_line_ending(g)
|
str_to_line_ending(g)
|
||||||
} else {
|
} else {
|
||||||
LineEnding::None
|
LineEnding::None
|
||||||
|
@ -172,18 +167,17 @@ impl<T: LineFormatter> Editor<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn auto_detect_indentation_style(&mut self) {
|
pub fn auto_detect_indentation_style(&mut self) {
|
||||||
let mut tab_blocks: usize = 0;
|
let mut tab_blocks: usize = 0;
|
||||||
let mut space_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 space_histogram: [usize; 9] = [0, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||||
|
|
||||||
let mut last_indent = (false, 0usize); // (was_tabs, indent_count)
|
let mut last_indent = (false, 0usize); // (was_tabs, indent_count)
|
||||||
|
|
||||||
// Collect statistics
|
// Collect statistics
|
||||||
let mut line_i: usize = 0;
|
let mut line_i: usize = 0;
|
||||||
for line in self.buffer.line_iter() {
|
for line in self.buffer.line_iter() {
|
||||||
let mut g_iter = line.grapheme_iter();
|
let mut g_iter = line.graphemes();
|
||||||
match g_iter.next() {
|
match g_iter.next() {
|
||||||
Some("\t") => {
|
Some("\t") => {
|
||||||
// Count leading tabs
|
// Count leading tabs
|
||||||
|
@ -263,13 +257,11 @@ impl<T: LineFormatter> Editor<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn update_dim(&mut self, h: usize, w: usize) {
|
pub fn update_dim(&mut self, h: usize, w: usize) {
|
||||||
self.editor_dim = (h, w);
|
self.editor_dim = (h, w);
|
||||||
self.update_view_dim();
|
self.update_view_dim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn update_view_dim(&mut self) {
|
pub fn update_view_dim(&mut self) {
|
||||||
// TODO: generalize for non-terminal UI. Maybe this isn't where it
|
// 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
|
// belongs, in fact. But for now, this is the easiest place to put
|
||||||
|
@ -277,11 +269,12 @@ impl<T: LineFormatter> Editor<T> {
|
||||||
let line_count_digits = digit_count(self.buffer.line_count() as u32, 10) as usize;
|
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
|
// Minus 1 vertically for the header, minus one more than the digits in
|
||||||
// the line count for the gutter.
|
// the line count for the gutter.
|
||||||
self.view_dim = (self.editor_dim.0 - 1,
|
self.view_dim = (
|
||||||
self.editor_dim.1 - line_count_digits - 1);
|
self.editor_dim.0 - 1,
|
||||||
|
self.editor_dim.1 - line_count_digits - 1,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn undo(&mut self) {
|
pub fn undo(&mut self) {
|
||||||
// TODO: handle multiple cursors properly
|
// TODO: handle multiple cursors properly
|
||||||
if let Some(pos) = self.buffer.undo() {
|
if let Some(pos) = self.buffer.undo() {
|
||||||
|
@ -298,7 +291,6 @@ impl<T: LineFormatter> Editor<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn redo(&mut self) {
|
pub fn redo(&mut self) {
|
||||||
// TODO: handle multiple cursors properly
|
// TODO: handle multiple cursors properly
|
||||||
if let Some(pos) = self.buffer.redo() {
|
if let Some(pos) = self.buffer.redo() {
|
||||||
|
@ -315,7 +307,6 @@ impl<T: LineFormatter> Editor<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Moves the editor's view the minimum amount to show the cursor
|
/// Moves the editor's view the minimum amount to show the cursor
|
||||||
pub fn move_view_to_cursor(&mut self) {
|
pub fn move_view_to_cursor(&mut self) {
|
||||||
// TODO: account for the horizontal offset of the editor view.
|
// TODO: account for the horizontal offset of the editor view.
|
||||||
|
@ -325,27 +316,32 @@ impl<T: LineFormatter> Editor<T> {
|
||||||
// the closest cursor.
|
// the closest cursor.
|
||||||
|
|
||||||
// Find the first and last grapheme index visible within the editor.
|
// Find the first and last grapheme index visible within the editor.
|
||||||
let g_first = self.formatter
|
let g_first =
|
||||||
.index_set_horizontal_v2d(&self.buffer, self.view_pos.0, 0, Floor);
|
self.formatter
|
||||||
let mut g_last = self.formatter.index_offset_vertical_v2d(&self.buffer,
|
.index_set_horizontal_v2d(&self.buffer, self.view_pos.0, 0, Floor);
|
||||||
g_first,
|
let mut g_last = self.formatter.index_offset_vertical_v2d(
|
||||||
self.view_dim.0 as isize,
|
&self.buffer,
|
||||||
(Floor, Floor));
|
g_first,
|
||||||
g_last = self.formatter
|
self.view_dim.0 as isize,
|
||||||
.index_set_horizontal_v2d(&self.buffer, g_last, self.view_dim.1, Floor);
|
(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
|
// Adjust the view depending on where the cursor is
|
||||||
if self.cursors[0].range.0 < g_first {
|
if self.cursors[0].range.0 < g_first {
|
||||||
self.view_pos.0 = self.cursors[0].range.0;
|
self.view_pos.0 = self.cursors[0].range.0;
|
||||||
} else if self.cursors[0].range.0 > g_last {
|
} else if self.cursors[0].range.0 > g_last {
|
||||||
self.view_pos.0 = self.formatter.index_offset_vertical_v2d(&self.buffer,
|
self.view_pos.0 = self.formatter.index_offset_vertical_v2d(
|
||||||
self.cursors[0].range.0,
|
&self.buffer,
|
||||||
-(self.view_dim.0 as isize),
|
self.cursors[0].range.0,
|
||||||
(Floor, Floor));
|
-(self.view_dim.0 as isize),
|
||||||
|
(Floor, Floor),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn insert_text_at_cursor(&mut self, text: &str) {
|
pub fn insert_text_at_cursor(&mut self, text: &str) {
|
||||||
self.cursors.make_consistent();
|
self.cursors.make_consistent();
|
||||||
|
|
||||||
|
@ -370,7 +366,6 @@ impl<T: LineFormatter> Editor<T> {
|
||||||
self.move_view_to_cursor();
|
self.move_view_to_cursor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn insert_tab_at_cursor(&mut self) {
|
pub fn insert_tab_at_cursor(&mut self) {
|
||||||
self.cursors.make_consistent();
|
self.cursors.make_consistent();
|
||||||
|
|
||||||
|
@ -383,16 +378,17 @@ impl<T: LineFormatter> Editor<T> {
|
||||||
c.range.1 += offset;
|
c.range.1 += offset;
|
||||||
|
|
||||||
// Figure out how many spaces to insert
|
// Figure out how many spaces to insert
|
||||||
let vis_pos = self.formatter.index_to_horizontal_v2d(&self.buffer, c.range.0);
|
let vis_pos = self.formatter
|
||||||
|
.index_to_horizontal_v2d(&self.buffer, c.range.0);
|
||||||
// TODO: handle tab settings
|
// TODO: handle tab settings
|
||||||
let next_tab_stop = ((vis_pos / self.soft_tab_width as usize) + 1) *
|
let next_tab_stop =
|
||||||
self.soft_tab_width as usize;
|
((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);
|
let space_count = min(next_tab_stop - vis_pos, 8);
|
||||||
|
|
||||||
|
|
||||||
// Insert spaces
|
// Insert spaces
|
||||||
let space_strs = ["", " ", " ", " ", " ", " ", " ", " ",
|
let space_strs = [
|
||||||
" "];
|
"", " ", " ", " ", " ", " ", " ", " ", " "
|
||||||
|
];
|
||||||
self.buffer.insert_text(space_strs[space_count], c.range.0);
|
self.buffer.insert_text(space_strs[space_count], c.range.0);
|
||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
|
|
||||||
|
@ -412,24 +408,17 @@ impl<T: LineFormatter> Editor<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn backspace_at_cursor(&mut self) {
|
pub fn backspace_at_cursor(&mut self) {
|
||||||
self.remove_text_behind_cursor(1);
|
self.remove_text_behind_cursor(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn insert_text_at_grapheme(&mut self, text: &str, pos: usize) {
|
pub fn insert_text_at_grapheme(&mut self, text: &str, pos: usize) {
|
||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
let buf_len = self.buffer.grapheme_count();
|
let buf_len = self.buffer.grapheme_count();
|
||||||
self.buffer.insert_text(text,
|
self.buffer
|
||||||
if pos < buf_len {
|
.insert_text(text, if pos < buf_len { pos } else { buf_len });
|
||||||
pos
|
|
||||||
} else {
|
|
||||||
buf_len
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn remove_text_behind_cursor(&mut self, grapheme_count: usize) {
|
pub fn remove_text_behind_cursor(&mut self, grapheme_count: usize) {
|
||||||
self.cursors.make_consistent();
|
self.cursors.make_consistent();
|
||||||
|
|
||||||
|
@ -466,7 +455,6 @@ impl<T: LineFormatter> Editor<T> {
|
||||||
self.move_view_to_cursor();
|
self.move_view_to_cursor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn remove_text_in_front_of_cursor(&mut self, grapheme_count: usize) {
|
pub fn remove_text_in_front_of_cursor(&mut self, grapheme_count: usize) {
|
||||||
self.cursors.make_consistent();
|
self.cursors.make_consistent();
|
||||||
|
|
||||||
|
@ -506,7 +494,6 @@ impl<T: LineFormatter> Editor<T> {
|
||||||
self.move_view_to_cursor();
|
self.move_view_to_cursor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn remove_text_inside_cursor(&mut self) {
|
pub fn remove_text_inside_cursor(&mut self) {
|
||||||
self.cursors.make_consistent();
|
self.cursors.make_consistent();
|
||||||
|
|
||||||
|
@ -521,7 +508,8 @@ impl<T: LineFormatter> Editor<T> {
|
||||||
if c.range.0 < c.range.1 {
|
if c.range.0 < c.range.1 {
|
||||||
let len = c.range.1 - c.range.0;
|
let len = c.range.1 - c.range.0;
|
||||||
|
|
||||||
self.buffer.remove_text_before(c.range.0, c.range.1 - c.range.0);
|
self.buffer
|
||||||
|
.remove_text_before(c.range.0, c.range.1 - c.range.0);
|
||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
|
|
||||||
// Move cursor
|
// Move cursor
|
||||||
|
@ -540,7 +528,6 @@ impl<T: LineFormatter> Editor<T> {
|
||||||
self.move_view_to_cursor();
|
self.move_view_to_cursor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn cursor_to_beginning_of_buffer(&mut self) {
|
pub fn cursor_to_beginning_of_buffer(&mut self) {
|
||||||
self.cursors = CursorSet::new();
|
self.cursors = CursorSet::new();
|
||||||
|
|
||||||
|
@ -551,7 +538,6 @@ impl<T: LineFormatter> Editor<T> {
|
||||||
self.move_view_to_cursor();
|
self.move_view_to_cursor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn cursor_to_end_of_buffer(&mut self) {
|
pub fn cursor_to_end_of_buffer(&mut self) {
|
||||||
let end = self.buffer.grapheme_count();
|
let end = self.buffer.grapheme_count();
|
||||||
|
|
||||||
|
@ -563,7 +549,6 @@ impl<T: LineFormatter> Editor<T> {
|
||||||
self.move_view_to_cursor();
|
self.move_view_to_cursor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn cursor_left(&mut self, n: usize) {
|
pub fn cursor_left(&mut self, n: usize) {
|
||||||
for c in self.cursors.iter_mut() {
|
for c in self.cursors.iter_mut() {
|
||||||
if c.range.0 >= n {
|
if c.range.0 >= n {
|
||||||
|
@ -580,7 +565,6 @@ impl<T: LineFormatter> Editor<T> {
|
||||||
self.move_view_to_cursor();
|
self.move_view_to_cursor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn cursor_right(&mut self, n: usize) {
|
pub fn cursor_right(&mut self, n: usize) {
|
||||||
for c in self.cursors.iter_mut() {
|
for c in self.cursors.iter_mut() {
|
||||||
c.range.1 += n;
|
c.range.1 += n;
|
||||||
|
@ -597,22 +581,25 @@ impl<T: LineFormatter> Editor<T> {
|
||||||
self.move_view_to_cursor();
|
self.move_view_to_cursor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn cursor_up(&mut self, n: usize) {
|
pub fn cursor_up(&mut self, n: usize) {
|
||||||
for c in self.cursors.iter_mut() {
|
for c in self.cursors.iter_mut() {
|
||||||
let vmove = -1 * (n * self.formatter.single_line_height()) as isize;
|
let vmove = -1 * (n * self.formatter.single_line_height()) as isize;
|
||||||
let mut temp_index = self.formatter.index_offset_vertical_v2d(&self.buffer,
|
let mut temp_index = self.formatter.index_offset_vertical_v2d(
|
||||||
c.range.0,
|
&self.buffer,
|
||||||
vmove,
|
c.range.0,
|
||||||
(Round, Round));
|
vmove,
|
||||||
|
(Round, Round),
|
||||||
|
);
|
||||||
|
|
||||||
if temp_index == 0 {
|
if temp_index == 0 {
|
||||||
c.update_vis_start(&(self.buffer), &(self.formatter));
|
c.update_vis_start(&(self.buffer), &(self.formatter));
|
||||||
} else {
|
} else {
|
||||||
temp_index = self.formatter.index_set_horizontal_v2d(&self.buffer,
|
temp_index = self.formatter.index_set_horizontal_v2d(
|
||||||
temp_index,
|
&self.buffer,
|
||||||
c.vis_start,
|
temp_index,
|
||||||
Round);
|
c.vis_start,
|
||||||
|
Round,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
c.range.0 = temp_index;
|
c.range.0 = temp_index;
|
||||||
|
@ -623,22 +610,25 @@ impl<T: LineFormatter> Editor<T> {
|
||||||
self.move_view_to_cursor();
|
self.move_view_to_cursor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn cursor_down(&mut self, n: usize) {
|
pub fn cursor_down(&mut self, n: usize) {
|
||||||
for c in self.cursors.iter_mut() {
|
for c in self.cursors.iter_mut() {
|
||||||
let vmove = (n * self.formatter.single_line_height()) as isize;
|
let vmove = (n * self.formatter.single_line_height()) as isize;
|
||||||
let mut temp_index = self.formatter.index_offset_vertical_v2d(&self.buffer,
|
let mut temp_index = self.formatter.index_offset_vertical_v2d(
|
||||||
c.range.0,
|
&self.buffer,
|
||||||
vmove,
|
c.range.0,
|
||||||
(Round, Round));
|
vmove,
|
||||||
|
(Round, Round),
|
||||||
|
);
|
||||||
|
|
||||||
if temp_index == self.buffer.grapheme_count() {
|
if temp_index == self.buffer.grapheme_count() {
|
||||||
c.update_vis_start(&(self.buffer), &(self.formatter));
|
c.update_vis_start(&(self.buffer), &(self.formatter));
|
||||||
} else {
|
} else {
|
||||||
temp_index = self.formatter.index_set_horizontal_v2d(&self.buffer,
|
temp_index = self.formatter.index_set_horizontal_v2d(
|
||||||
temp_index,
|
&self.buffer,
|
||||||
c.vis_start,
|
temp_index,
|
||||||
Round);
|
c.vis_start,
|
||||||
|
Round,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
c.range.0 = temp_index;
|
c.range.0 = temp_index;
|
||||||
|
@ -649,14 +639,15 @@ impl<T: LineFormatter> Editor<T> {
|
||||||
self.move_view_to_cursor();
|
self.move_view_to_cursor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn page_up(&mut self) {
|
pub fn page_up(&mut self) {
|
||||||
let move_amount = self.view_dim.0 -
|
let move_amount =
|
||||||
max((self.view_dim.0 / 8), self.formatter.single_line_height());
|
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 = self.formatter.index_offset_vertical_v2d(
|
||||||
self.view_pos.0,
|
&self.buffer,
|
||||||
-1 * move_amount as isize,
|
self.view_pos.0,
|
||||||
(Round, Round));
|
-1 * move_amount as isize,
|
||||||
|
(Round, Round),
|
||||||
|
);
|
||||||
|
|
||||||
self.cursor_up(move_amount);
|
self.cursor_up(move_amount);
|
||||||
|
|
||||||
|
@ -664,14 +655,15 @@ impl<T: LineFormatter> Editor<T> {
|
||||||
self.move_view_to_cursor();
|
self.move_view_to_cursor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn page_down(&mut self) {
|
pub fn page_down(&mut self) {
|
||||||
let move_amount = self.view_dim.0 -
|
let move_amount =
|
||||||
max((self.view_dim.0 / 8), self.formatter.single_line_height());
|
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 = self.formatter.index_offset_vertical_v2d(
|
||||||
self.view_pos.0,
|
&self.buffer,
|
||||||
move_amount as isize,
|
self.view_pos.0,
|
||||||
(Round, Round));
|
move_amount as isize,
|
||||||
|
(Round, Round),
|
||||||
|
);
|
||||||
|
|
||||||
self.cursor_down(move_amount);
|
self.cursor_down(move_amount);
|
||||||
|
|
||||||
|
@ -679,15 +671,15 @@ impl<T: LineFormatter> Editor<T> {
|
||||||
self.move_view_to_cursor();
|
self.move_view_to_cursor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn jump_to_line(&mut self, n: usize) {
|
pub fn jump_to_line(&mut self, n: usize) {
|
||||||
let pos = self.buffer.line_col_to_index((n, 0));
|
let pos = self.buffer.line_col_to_index((n, 0));
|
||||||
self.cursors.truncate(1);
|
self.cursors.truncate(1);
|
||||||
self.cursors[0].range.0 = self.formatter.index_set_horizontal_v2d(&self.buffer,
|
self.cursors[0].range.0 = self.formatter.index_set_horizontal_v2d(
|
||||||
pos,
|
&self.buffer,
|
||||||
self.cursors[0]
|
pos,
|
||||||
.vis_start,
|
self.cursors[0].vis_start,
|
||||||
Round);
|
Round,
|
||||||
|
);
|
||||||
self.cursors[0].range.1 = self.cursors[0].range.0;
|
self.cursors[0].range.1 = self.cursors[0].range.0;
|
||||||
|
|
||||||
// Adjust view
|
// Adjust view
|
||||||
|
|
127
src/formatter.rs
127
src/formatter.rs
|
@ -9,7 +9,6 @@ use buffer::Buffer;
|
||||||
// lines.
|
// lines.
|
||||||
pub const LINE_BLOCK_LENGTH: usize = 4096;
|
pub const LINE_BLOCK_LENGTH: usize = 4096;
|
||||||
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq)]
|
#[derive(Copy, Clone, PartialEq)]
|
||||||
pub enum RoundingBehavior {
|
pub enum RoundingBehavior {
|
||||||
Round,
|
Round,
|
||||||
|
@ -17,31 +16,32 @@ pub enum RoundingBehavior {
|
||||||
Ceiling,
|
Ceiling,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub trait LineFormatter {
|
pub trait LineFormatter {
|
||||||
fn single_line_height(&self) -> usize;
|
fn single_line_height(&self) -> usize;
|
||||||
|
|
||||||
/// Returns the 2d visual dimensions of the given text when formatted
|
/// Returns the 2d visual dimensions of the given text when formatted
|
||||||
/// by the formatter.
|
/// by the formatter.
|
||||||
/// The text to be formatted is passed as a grapheme iterator.
|
/// 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<Item = &'a str>;
|
fn dimensions<'a, T>(&'a self, g_iter: T) -> (usize, usize)
|
||||||
|
where
|
||||||
|
T: Iterator<Item = &'a str>;
|
||||||
|
|
||||||
/// Converts a grapheme index within a text into a visual 2d position.
|
/// Converts a grapheme index within a text into a visual 2d position.
|
||||||
/// The text to be formatted is passed as a grapheme iterator.
|
/// 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)
|
fn index_to_v2d<'a, T>(&'a self, g_iter: T, index: usize) -> (usize, usize)
|
||||||
where T: Iterator<Item = &'a str>;
|
where
|
||||||
|
T: Iterator<Item = &'a str>;
|
||||||
|
|
||||||
/// Converts a visual 2d position into a grapheme index within a text.
|
/// Converts a visual 2d position into a grapheme index within a text.
|
||||||
/// The text to be formatted is passed as a grapheme iterator.
|
/// The text to be formatted is passed as a grapheme iterator.
|
||||||
fn v2d_to_index<'a, T>(&'a self,
|
fn v2d_to_index<'a, T>(
|
||||||
g_iter: T,
|
&'a self,
|
||||||
v2d: (usize, usize),
|
g_iter: T,
|
||||||
rounding: (RoundingBehavior, RoundingBehavior))
|
v2d: (usize, usize),
|
||||||
-> usize
|
rounding: (RoundingBehavior, RoundingBehavior),
|
||||||
where T: Iterator<Item = &'a str>;
|
) -> usize
|
||||||
|
where
|
||||||
|
T: Iterator<Item = &'a str>;
|
||||||
|
|
||||||
fn index_to_horizontal_v2d(&self, buf: &Buffer, index: usize) -> usize {
|
fn index_to_horizontal_v2d(&self, buf: &Buffer, index: usize) -> usize {
|
||||||
let (line_i, col_i) = buf.index_to_line_col(index);
|
let (line_i, col_i) = buf.index_to_line_col(index);
|
||||||
|
@ -52,20 +52,20 @@ pub trait LineFormatter {
|
||||||
|
|
||||||
// Get an iter into the right block
|
// Get an iter into the right block
|
||||||
let a = line_block * LINE_BLOCK_LENGTH;
|
let a = line_block * LINE_BLOCK_LENGTH;
|
||||||
let b = min(line.grapheme_count(), (line_block + 1) * LINE_BLOCK_LENGTH);
|
let b = min(line.len_chars(), (line_block + 1) * LINE_BLOCK_LENGTH);
|
||||||
let g_iter = line.grapheme_iter_between_indices(a, b);
|
let g_iter = line.slice(a, b).graphemes();
|
||||||
return self.index_to_v2d(g_iter, col_i_adjusted).1;
|
return self.index_to_v2d(g_iter, col_i_adjusted).1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Takes a grapheme index and a visual vertical offset, and returns the grapheme
|
/// Takes a grapheme index and a visual vertical offset, and returns the grapheme
|
||||||
/// index after that visual offset is applied.
|
/// index after that visual offset is applied.
|
||||||
fn index_offset_vertical_v2d(&self,
|
fn index_offset_vertical_v2d(
|
||||||
buf: &Buffer,
|
&self,
|
||||||
index: usize,
|
buf: &Buffer,
|
||||||
offset: isize,
|
index: usize,
|
||||||
rounding: (RoundingBehavior, RoundingBehavior))
|
offset: isize,
|
||||||
-> usize {
|
rounding: (RoundingBehavior, RoundingBehavior),
|
||||||
|
) -> usize {
|
||||||
// TODO: handle rounding modes
|
// TODO: handle rounding modes
|
||||||
// TODO: do this with bidirectional line iterator
|
// TODO: do this with bidirectional line iterator
|
||||||
|
|
||||||
|
@ -76,12 +76,13 @@ pub trait LineFormatter {
|
||||||
let (line_block, col_i_adjusted) = block_index_and_offset(col_i);
|
let (line_block, col_i_adjusted) = block_index_and_offset(col_i);
|
||||||
|
|
||||||
let mut line = buf.get_line(line_i);
|
let mut line = buf.get_line(line_i);
|
||||||
let (mut y, x) =
|
let (mut y, x) = self.index_to_v2d(
|
||||||
self.index_to_v2d(line.grapheme_iter_between_indices(line_block * LINE_BLOCK_LENGTH,
|
line.slice(
|
||||||
min(line.grapheme_count(),
|
line_block * LINE_BLOCK_LENGTH,
|
||||||
(line_block + 1) *
|
min(line.len_chars(), (line_block + 1) * LINE_BLOCK_LENGTH),
|
||||||
LINE_BLOCK_LENGTH)),
|
).graphemes(),
|
||||||
col_i_adjusted);
|
col_i_adjusted,
|
||||||
|
);
|
||||||
|
|
||||||
// First, find the right line while keeping track of the vertical offset
|
// First, find the right line while keeping track of the vertical offset
|
||||||
let mut new_y = y as isize + offset;
|
let mut new_y = y as isize + offset;
|
||||||
|
@ -89,19 +90,17 @@ pub trait LineFormatter {
|
||||||
let mut block_index: usize = line_block;
|
let mut block_index: usize = line_block;
|
||||||
loop {
|
loop {
|
||||||
line = buf.get_line(line_i);
|
line = buf.get_line(line_i);
|
||||||
let (h, _) =
|
let (h, _) = self.dimensions(line.slice(
|
||||||
self.dimensions(line.grapheme_iter_between_indices(block_index *
|
block_index * LINE_BLOCK_LENGTH,
|
||||||
LINE_BLOCK_LENGTH,
|
min(line.len_chars(), (block_index + 1) * LINE_BLOCK_LENGTH),
|
||||||
min(line.grapheme_count(),
|
).graphemes());
|
||||||
(block_index + 1) *
|
|
||||||
LINE_BLOCK_LENGTH)));
|
|
||||||
|
|
||||||
if new_y >= 0 && new_y < h as isize {
|
if new_y >= 0 && new_y < h as isize {
|
||||||
y = new_y as usize;
|
y = new_y as usize;
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
if new_y > 0 {
|
if new_y > 0 {
|
||||||
let is_last_block = block_index >= last_block_index(line.grapheme_count());
|
let is_last_block = block_index >= last_block_index(line.len_chars());
|
||||||
|
|
||||||
// Check for off-the-end
|
// Check for off-the-end
|
||||||
if is_last_block && (line_i + 1) >= buf.line_count() {
|
if is_last_block && (line_i + 1) >= buf.line_count() {
|
||||||
|
@ -124,11 +123,14 @@ pub trait LineFormatter {
|
||||||
if block_index == 0 {
|
if block_index == 0 {
|
||||||
line_i -= 1;
|
line_i -= 1;
|
||||||
line = buf.get_line(line_i);
|
line = buf.get_line(line_i);
|
||||||
block_index = last_block_index(line.grapheme_count());
|
block_index = last_block_index(line.len_chars());
|
||||||
} else {
|
} else {
|
||||||
block_index -= 1;
|
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)));
|
let (h, _) = self.dimensions(line.slice(
|
||||||
|
block_index * LINE_BLOCK_LENGTH,
|
||||||
|
min(line.len_chars(), (block_index + 1) * LINE_BLOCK_LENGTH),
|
||||||
|
).graphemes());
|
||||||
new_y += h as isize;
|
new_y += h as isize;
|
||||||
} else {
|
} else {
|
||||||
unreachable!();
|
unreachable!();
|
||||||
|
@ -138,52 +140,57 @@ pub trait LineFormatter {
|
||||||
|
|
||||||
// Next, convert the resulting coordinates back into buffer-wide
|
// Next, convert the resulting coordinates back into buffer-wide
|
||||||
// coordinates.
|
// coordinates.
|
||||||
let block_slice = line.slice(block_index * LINE_BLOCK_LENGTH,
|
let block_slice = line.slice(
|
||||||
min(line.grapheme_count(),
|
block_index * LINE_BLOCK_LENGTH,
|
||||||
(block_index + 1) * LINE_BLOCK_LENGTH));
|
min(line.len_chars(), (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_col_i = min(
|
||||||
|
self.v2d_to_index(block_slice.graphemes(), (y, x), rounding),
|
||||||
|
LINE_BLOCK_LENGTH - 1,
|
||||||
|
);
|
||||||
col_i = (block_index * LINE_BLOCK_LENGTH) + block_col_i;
|
col_i = (block_index * LINE_BLOCK_LENGTH) + block_col_i;
|
||||||
|
|
||||||
return buf.line_col_to_index((line_i, col_i));
|
return buf.line_col_to_index((line_i, col_i));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Takes a grapheme index and a desired visual horizontal position, and
|
/// Takes a grapheme index and a desired visual horizontal position, and
|
||||||
/// returns a grapheme index on the same visual line as the given index,
|
/// returns a grapheme index on the same visual line as the given index,
|
||||||
/// but offset to have the desired horizontal position.
|
/// but offset to have the desired horizontal position.
|
||||||
fn index_set_horizontal_v2d(&self,
|
fn index_set_horizontal_v2d(
|
||||||
buf: &Buffer,
|
&self,
|
||||||
index: usize,
|
buf: &Buffer,
|
||||||
horizontal: usize,
|
index: usize,
|
||||||
rounding: RoundingBehavior)
|
horizontal: usize,
|
||||||
-> usize {
|
rounding: RoundingBehavior,
|
||||||
|
) -> usize {
|
||||||
let (line_i, col_i) = buf.index_to_line_col(index);
|
let (line_i, col_i) = buf.index_to_line_col(index);
|
||||||
let line = buf.get_line(line_i);
|
let line = buf.get_line(line_i);
|
||||||
|
|
||||||
// Find the right block in the line, and the index within that block
|
// 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 (line_block, col_i_adjusted) = block_index_and_offset(col_i);
|
||||||
let start_index = line_block * LINE_BLOCK_LENGTH;
|
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.len_chars(), start_index + LINE_BLOCK_LENGTH);
|
||||||
|
|
||||||
// Calculate the horizontal position
|
// Calculate the horizontal position
|
||||||
let (v, _) = self.index_to_v2d(line.grapheme_iter_between_indices(start_index, end_index),
|
let (v, _) = self.index_to_v2d(
|
||||||
col_i_adjusted);
|
line.slice(start_index, end_index).graphemes(),
|
||||||
let block_col_i = self.v2d_to_index(line.grapheme_iter_between_indices(start_index,
|
col_i_adjusted,
|
||||||
end_index),
|
);
|
||||||
(v, horizontal),
|
let block_col_i = self.v2d_to_index(
|
||||||
(RoundingBehavior::Floor, rounding));
|
line.slice(start_index, end_index).graphemes(),
|
||||||
|
(v, horizontal),
|
||||||
|
(RoundingBehavior::Floor, rounding),
|
||||||
|
);
|
||||||
let mut new_col_i = start_index + min(block_col_i, LINE_BLOCK_LENGTH - 1);
|
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
|
// 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() &&
|
if (line_i + 1) < buf.line_count() && new_col_i >= line.len_chars() && line.len_chars() > 0
|
||||||
line.grapheme_count() > 0 {
|
{
|
||||||
new_col_i = line.grapheme_count() - 1;
|
new_col_i = line.len_chars() - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (index + new_col_i) - col_i;
|
return (index + new_col_i) - col_i;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn block_index_and_offset(index: usize) -> (usize, usize) {
|
pub fn block_index_and_offset(index: usize) -> (usize, usize) {
|
||||||
|
|
18
src/main.rs
18
src/main.rs
|
@ -1,13 +1,13 @@
|
||||||
// #![feature(test)]
|
// #![feature(test)]
|
||||||
|
|
||||||
// extern crate test;
|
// extern crate test;
|
||||||
extern crate rustbox;
|
|
||||||
extern crate docopt;
|
extern crate docopt;
|
||||||
|
extern crate encoding;
|
||||||
|
extern crate ropey;
|
||||||
|
extern crate rustbox;
|
||||||
extern crate rustc_serialize;
|
extern crate rustc_serialize;
|
||||||
extern crate unicode_segmentation;
|
extern crate unicode_segmentation;
|
||||||
extern crate unicode_width;
|
extern crate unicode_width;
|
||||||
extern crate encoding;
|
|
||||||
extern crate ropey;
|
|
||||||
// extern crate freetype;
|
// extern crate freetype;
|
||||||
// extern crate sdl2;
|
// extern crate sdl2;
|
||||||
|
|
||||||
|
@ -28,9 +28,6 @@ mod term_ui;
|
||||||
// mod font;
|
// mod font;
|
||||||
// mod gui;
|
// mod gui;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Usage documentation string
|
// Usage documentation string
|
||||||
static USAGE: &'static str = "
|
static USAGE: &'static str = "
|
||||||
Usage: led [options] [<file>]
|
Usage: led [options] [<file>]
|
||||||
|
@ -41,7 +38,6 @@ Options:
|
||||||
-h, --help Show this message
|
-h, --help Show this message
|
||||||
";
|
";
|
||||||
|
|
||||||
|
|
||||||
// Struct for storing command-line arguments
|
// Struct for storing command-line arguments
|
||||||
#[derive(RustcDecodable, Debug)]
|
#[derive(RustcDecodable, Debug)]
|
||||||
struct Args {
|
struct Args {
|
||||||
|
@ -50,13 +46,11 @@ struct Args {
|
||||||
flag_help: bool,
|
flag_help: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// Get command-line arguments
|
// Get command-line arguments
|
||||||
let args: Args = Docopt::new(USAGE).and_then(|d| d.decode()).unwrap_or_else(|e| e.exit());
|
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 {
|
if args.flag_gui {
|
||||||
|
|
|
@ -4,17 +4,10 @@
|
||||||
use std::iter::repeat;
|
use std::iter::repeat;
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
|
|
||||||
pub fn is_line_ending(text: &str) -> bool {
|
pub fn is_line_ending(text: &str) -> bool {
|
||||||
match text {
|
match text {
|
||||||
"\u{000D}\u{000A}" |
|
"\u{000D}\u{000A}" | "\u{000A}" | "\u{000B}" | "\u{000C}" | "\u{000D}" | "\u{0085}"
|
||||||
"\u{000A}" |
|
| "\u{2028}" | "\u{2029}" => true,
|
||||||
"\u{000B}" |
|
|
||||||
"\u{000C}" |
|
|
||||||
"\u{000D}" |
|
|
||||||
"\u{0085}" |
|
|
||||||
"\u{2028}" |
|
|
||||||
"\u{2029}" => true,
|
|
||||||
|
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
|
@ -164,7 +157,6 @@ pub fn insert_text_at_grapheme_index(s: &mut String, text: &str, pos: usize) {
|
||||||
// TODO: use copy_memory()
|
// TODO: use copy_memory()
|
||||||
let mut i = byte_pos;
|
let mut i = byte_pos;
|
||||||
for g in UnicodeSegmentation::graphemes(text, true) {
|
for g in UnicodeSegmentation::graphemes(text, true) {
|
||||||
|
|
||||||
for b in g.bytes() {
|
for b in g.bytes() {
|
||||||
byte_vec[i] = b;
|
byte_vec[i] = b;
|
||||||
i += 1
|
i += 1
|
||||||
|
@ -175,8 +167,10 @@ 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.
|
/// 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) {
|
pub fn remove_text_between_grapheme_indices(s: &mut String, pos_a: usize, pos_b: usize) {
|
||||||
// Bounds checks
|
// Bounds checks
|
||||||
assert!(pos_a <= pos_b,
|
assert!(
|
||||||
"remove_text_between_grapheme_indices(): pos_a must be less than or equal to pos_b.");
|
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 {
|
if pos_a == pos_b {
|
||||||
return;
|
return;
|
||||||
|
@ -225,25 +219,19 @@ pub fn split_string_at_grapheme_index(s1: &mut String, pos: usize) -> String {
|
||||||
return s2;
|
return s2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// Represents one of the valid Unicode line endings.
|
/// Represents one of the valid Unicode line endings.
|
||||||
/// Also acts as an index into `LINE_ENDINGS`.
|
/// Also acts as an index into `LINE_ENDINGS`.
|
||||||
#[derive(PartialEq, Copy, Clone)]
|
#[derive(PartialEq, Copy, Clone)]
|
||||||
pub enum LineEnding {
|
pub enum LineEnding {
|
||||||
None = 0, // No line ending
|
None = 0, // No line ending
|
||||||
CRLF = 1, // CarriageReturn followed by LineFeed
|
CRLF = 1, // CarriageReturn followed by LineFeed
|
||||||
LF = 2, // U+000A -- LineFeed
|
LF = 2, // U+000A -- LineFeed
|
||||||
VT = 3, // U+000B -- VerticalTab
|
VT = 3, // U+000B -- VerticalTab
|
||||||
FF = 4, // U+000C -- FormFeed
|
FF = 4, // U+000C -- FormFeed
|
||||||
CR = 5, // U+000D -- CarriageReturn
|
CR = 5, // U+000D -- CarriageReturn
|
||||||
NEL = 6, // U+0085 -- NextLine
|
NEL = 6, // U+0085 -- NextLine
|
||||||
LS = 7, // U+2028 -- Line Separator
|
LS = 7, // U+2028 -- Line Separator
|
||||||
PS = 8, // U+2029 -- ParagraphSeparator
|
PS = 8, // U+2029 -- ParagraphSeparator
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn str_to_line_ending(g: &str) -> LineEnding {
|
pub fn str_to_line_ending(g: &str) -> LineEnding {
|
||||||
|
@ -305,12 +293,14 @@ pub fn line_ending_to_str(ending: LineEnding) -> &'static str {
|
||||||
|
|
||||||
/// An array of string literals corresponding to the possible
|
/// An array of string literals corresponding to the possible
|
||||||
/// unicode line endings.
|
/// unicode line endings.
|
||||||
pub const LINE_ENDINGS: [&'static str; 9] = ["",
|
pub const LINE_ENDINGS: [&'static str; 9] = [
|
||||||
"\u{000D}\u{000A}",
|
"",
|
||||||
"\u{000A}",
|
"\u{000D}\u{000A}",
|
||||||
"\u{000B}",
|
"\u{000A}",
|
||||||
"\u{000C}",
|
"\u{000B}",
|
||||||
"\u{000D}",
|
"\u{000C}",
|
||||||
"\u{0085}",
|
"\u{000D}",
|
||||||
"\u{2028}",
|
"\u{0085}",
|
||||||
"\u{2029}"];
|
"\u{2028}",
|
||||||
|
"\u{2029}",
|
||||||
|
];
|
||||||
|
|
|
@ -21,7 +21,6 @@ pub struct ConsoleLineFormatter {
|
||||||
pub wrap_additional_indent: usize,
|
pub wrap_additional_indent: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl ConsoleLineFormatter {
|
impl ConsoleLineFormatter {
|
||||||
pub fn new(tab_width: u8) -> ConsoleLineFormatter {
|
pub fn new(tab_width: u8) -> ConsoleLineFormatter {
|
||||||
ConsoleLineFormatter {
|
ConsoleLineFormatter {
|
||||||
|
@ -47,7 +46,8 @@ impl ConsoleLineFormatter {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn iter<'a, T>(&'a self, g_iter: T) -> ConsoleLineFormatterVisIter<'a, T>
|
pub fn iter<'a, T>(&'a self, g_iter: T) -> ConsoleLineFormatterVisIter<'a, T>
|
||||||
where T: Iterator<Item = &'a str>
|
where
|
||||||
|
T: Iterator<Item = &'a str>,
|
||||||
{
|
{
|
||||||
ConsoleLineFormatterVisIter::<'a, T> {
|
ConsoleLineFormatterVisIter::<'a, T> {
|
||||||
grapheme_iter: g_iter,
|
grapheme_iter: g_iter,
|
||||||
|
@ -61,15 +61,14 @@ impl ConsoleLineFormatter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl LineFormatter for ConsoleLineFormatter {
|
impl LineFormatter for ConsoleLineFormatter {
|
||||||
fn single_line_height(&self) -> usize {
|
fn single_line_height(&self) -> usize {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn dimensions<'a, T>(&'a self, g_iter: T) -> (usize, usize)
|
fn dimensions<'a, T>(&'a self, g_iter: T) -> (usize, usize)
|
||||||
where T: Iterator<Item = &'a str>
|
where
|
||||||
|
T: Iterator<Item = &'a str>,
|
||||||
{
|
{
|
||||||
let mut dim: (usize, usize) = (0, 0);
|
let mut dim: (usize, usize) = (0, 0);
|
||||||
|
|
||||||
|
@ -82,9 +81,9 @@ impl LineFormatter for ConsoleLineFormatter {
|
||||||
return dim;
|
return dim;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn index_to_v2d<'a, T>(&'a self, g_iter: T, index: usize) -> (usize, usize)
|
fn index_to_v2d<'a, T>(&'a self, g_iter: T, index: usize) -> (usize, usize)
|
||||||
where T: Iterator<Item = &'a str>
|
where
|
||||||
|
T: Iterator<Item = &'a str>,
|
||||||
{
|
{
|
||||||
let mut pos = (0, 0);
|
let mut pos = (0, 0);
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
|
@ -103,13 +102,14 @@ impl LineFormatter for ConsoleLineFormatter {
|
||||||
return (pos.0, pos.1 + last_width);
|
return (pos.0, pos.1 + last_width);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn v2d_to_index<'a, T>(
|
||||||
fn v2d_to_index<'a, T>(&'a self,
|
&'a self,
|
||||||
g_iter: T,
|
g_iter: T,
|
||||||
v2d: (usize, usize),
|
v2d: (usize, usize),
|
||||||
_: (RoundingBehavior, RoundingBehavior))
|
_: (RoundingBehavior, RoundingBehavior),
|
||||||
-> usize
|
) -> usize
|
||||||
where T: Iterator<Item = &'a str>
|
where
|
||||||
|
T: Iterator<Item = &'a str>,
|
||||||
{
|
{
|
||||||
// TODO: handle rounding modes
|
// TODO: handle rounding modes
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
|
@ -129,13 +129,13 @@ impl LineFormatter for ConsoleLineFormatter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
// An iterator that iterates over the graphemes in a line in a
|
// An iterator that iterates over the graphemes in a line in a
|
||||||
// manner consistent with the ConsoleFormatter.
|
// manner consistent with the ConsoleFormatter.
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
pub struct ConsoleLineFormatterVisIter<'a, T>
|
pub struct ConsoleLineFormatterVisIter<'a, T>
|
||||||
where T: Iterator<Item = &'a str>
|
where
|
||||||
|
T: Iterator<Item = &'a str>,
|
||||||
{
|
{
|
||||||
grapheme_iter: T,
|
grapheme_iter: T,
|
||||||
f: &'a ConsoleLineFormatter,
|
f: &'a ConsoleLineFormatter,
|
||||||
|
@ -148,9 +148,9 @@ pub struct ConsoleLineFormatterVisIter<'a, T>
|
||||||
word_i: usize,
|
word_i: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl<'a, T> ConsoleLineFormatterVisIter<'a, T>
|
impl<'a, T> ConsoleLineFormatterVisIter<'a, T>
|
||||||
where T: Iterator<Item = &'a str>
|
where
|
||||||
|
T: Iterator<Item = &'a str>,
|
||||||
{
|
{
|
||||||
fn next_nowrap(&mut self, g: &'a str) -> Option<(&'a str, (usize, usize), usize)> {
|
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 width = grapheme_vis_width_at_vis_pos(g, self.pos.1, self.f.tab_width as usize);
|
||||||
|
@ -160,11 +160,11 @@ impl<'a, T> ConsoleLineFormatterVisIter<'a, T>
|
||||||
return Some((g, pos, width));
|
return Some((g, pos, width));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn next_charwrap(
|
||||||
fn next_charwrap(&mut self,
|
&mut self,
|
||||||
g: &'a str,
|
g: &'a str,
|
||||||
wrap_width: usize)
|
wrap_width: usize,
|
||||||
-> Option<(&'a str, (usize, usize), 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);
|
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.pos.1 + width) > wrap_width {
|
||||||
|
@ -174,16 +174,24 @@ impl<'a, T> ConsoleLineFormatterVisIter<'a, T>
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.f.maintain_indent {
|
if self.f.maintain_indent {
|
||||||
let pos = (self.pos.0 + self.f.single_line_height(),
|
let pos = (
|
||||||
self.indent + self.f.wrap_additional_indent);
|
self.pos.0 + self.f.single_line_height(),
|
||||||
self.pos = (self.pos.0 + self.f.single_line_height(),
|
self.indent + self.f.wrap_additional_indent,
|
||||||
self.indent + self.f.wrap_additional_indent + width);
|
);
|
||||||
|
self.pos = (
|
||||||
|
self.pos.0 + self.f.single_line_height(),
|
||||||
|
self.indent + self.f.wrap_additional_indent + width,
|
||||||
|
);
|
||||||
return Some((g, pos, width));
|
return Some((g, pos, width));
|
||||||
} else {
|
} else {
|
||||||
let pos = (self.pos.0 + self.f.single_line_height(),
|
let pos = (
|
||||||
self.f.wrap_additional_indent);
|
self.pos.0 + self.f.single_line_height(),
|
||||||
self.pos = (self.pos.0 + self.f.single_line_height(),
|
self.f.wrap_additional_indent,
|
||||||
self.f.wrap_additional_indent + width);
|
);
|
||||||
|
self.pos = (
|
||||||
|
self.pos.0 + self.f.single_line_height(),
|
||||||
|
self.f.wrap_additional_indent + width,
|
||||||
|
);
|
||||||
return Some((g, pos, width));
|
return Some((g, pos, width));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -202,10 +210,9 @@ impl<'a, T> ConsoleLineFormatterVisIter<'a, T>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
impl<'a, T> Iterator for ConsoleLineFormatterVisIter<'a, T>
|
impl<'a, T> Iterator for ConsoleLineFormatterVisIter<'a, T>
|
||||||
where T: Iterator<Item = &'a str>
|
where
|
||||||
|
T: Iterator<Item = &'a str>,
|
||||||
{
|
{
|
||||||
type Item = (&'a str, (usize, usize), usize);
|
type Item = (&'a str, (usize, usize), usize);
|
||||||
|
|
||||||
|
@ -234,9 +241,11 @@ impl<'a, T> Iterator for ConsoleLineFormatterVisIter<'a, T>
|
||||||
self.word_buf.truncate(0);
|
self.word_buf.truncate(0);
|
||||||
while let Some(g) = self.grapheme_iter.next() {
|
while let Some(g) = self.grapheme_iter.next() {
|
||||||
self.word_buf.push(g);
|
self.word_buf.push(g);
|
||||||
let width = grapheme_vis_width_at_vis_pos(g,
|
let width = grapheme_vis_width_at_vis_pos(
|
||||||
self.pos.1 + word_width,
|
g,
|
||||||
self.f.tab_width as usize);
|
self.pos.1 + word_width,
|
||||||
|
self.f.tab_width as usize,
|
||||||
|
);
|
||||||
word_width += width;
|
word_width += width;
|
||||||
if is_whitespace(g) {
|
if is_whitespace(g) {
|
||||||
break;
|
break;
|
||||||
|
@ -257,11 +266,15 @@ impl<'a, T> Iterator for ConsoleLineFormatterVisIter<'a, T>
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.f.maintain_indent {
|
if self.f.maintain_indent {
|
||||||
self.pos = (self.pos.0 + self.f.single_line_height(),
|
self.pos = (
|
||||||
self.indent + self.f.wrap_additional_indent);
|
self.pos.0 + self.f.single_line_height(),
|
||||||
|
self.indent + self.f.wrap_additional_indent,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
self.pos = (self.pos.0 + self.f.single_line_height(),
|
self.pos = (
|
||||||
self.f.wrap_additional_indent);
|
self.pos.0 + self.f.single_line_height(),
|
||||||
|
self.f.wrap_additional_indent,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -277,8 +290,6 @@ impl<'a, T> Iterator for ConsoleLineFormatterVisIter<'a, T>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
// Helper functions
|
// Helper functions
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
@ -302,18 +313,15 @@ fn grapheme_vis_width_at_vis_pos(g: &str, pos: usize, tab_width: usize) -> usize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
#![allow(unused_imports)]
|
#![allow(unused_imports)]
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
use super::*;
|
use super::*;
|
||||||
use formatter::{LineFormatter, LINE_BLOCK_LENGTH};
|
use formatter::{LineFormatter, LINE_BLOCK_LENGTH};
|
||||||
use formatter::RoundingBehavior::{Round, Floor, Ceiling};
|
use formatter::RoundingBehavior::{Ceiling, Floor, Round};
|
||||||
use buffer::Buffer;
|
use buffer::Buffer;
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn dimensions_1() {
|
fn dimensions_1() {
|
||||||
let text = "Hello there, stranger!"; // 22 graphemes long
|
let text = "Hello there, stranger!"; // 22 graphemes long
|
||||||
|
@ -324,11 +332,12 @@ mod tests {
|
||||||
f.wrap_additional_indent = 0;
|
f.wrap_additional_indent = 0;
|
||||||
f.set_wrap_width(80);
|
f.set_wrap_width(80);
|
||||||
|
|
||||||
assert_eq!(f.dimensions(UnicodeSegmentation::graphemes(text, true)),
|
assert_eq!(
|
||||||
(1, 22));
|
f.dimensions(UnicodeSegmentation::graphemes(text, true)),
|
||||||
|
(1, 22)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn dimensions_2() {
|
fn dimensions_2() {
|
||||||
let text = "Hello there, stranger! How are you doing this fine day?"; // 56 graphemes long
|
let text = "Hello there, stranger! How are you doing this fine day?"; // 56 graphemes long
|
||||||
|
@ -339,11 +348,12 @@ mod tests {
|
||||||
f.wrap_additional_indent = 0;
|
f.wrap_additional_indent = 0;
|
||||||
f.set_wrap_width(12);
|
f.set_wrap_width(12);
|
||||||
|
|
||||||
assert_eq!(f.dimensions(UnicodeSegmentation::graphemes(text, true)),
|
assert_eq!(
|
||||||
(5, 12));
|
f.dimensions(UnicodeSegmentation::graphemes(text, true)),
|
||||||
|
(5, 12)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn index_to_v2d_1() {
|
fn index_to_v2d_1() {
|
||||||
let text = "Hello there, stranger!"; // 22 graphemes long
|
let text = "Hello there, stranger!"; // 22 graphemes long
|
||||||
|
@ -354,17 +364,24 @@ mod tests {
|
||||||
f.wrap_additional_indent = 0;
|
f.wrap_additional_indent = 0;
|
||||||
f.set_wrap_width(80);
|
f.set_wrap_width(80);
|
||||||
|
|
||||||
assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 0),
|
assert_eq!(
|
||||||
(0, 0));
|
f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 0),
|
||||||
assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 5),
|
(0, 0)
|
||||||
(0, 5));
|
);
|
||||||
assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 22),
|
assert_eq!(
|
||||||
(0, 22));
|
f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 5),
|
||||||
assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 23),
|
(0, 5)
|
||||||
(0, 22));
|
);
|
||||||
|
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]
|
#[test]
|
||||||
fn index_to_v2d_2() {
|
fn index_to_v2d_2() {
|
||||||
let text = "Hello there, stranger! How are you doing this fine day?"; // 56 graphemes long
|
let text = "Hello there, stranger! How are you doing this fine day?"; // 56 graphemes long
|
||||||
|
@ -375,46 +392,77 @@ mod tests {
|
||||||
f.wrap_additional_indent = 0;
|
f.wrap_additional_indent = 0;
|
||||||
f.set_wrap_width(12);
|
f.set_wrap_width(12);
|
||||||
|
|
||||||
assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 0),
|
assert_eq!(
|
||||||
(0, 0));
|
f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 0),
|
||||||
assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 5),
|
(0, 0)
|
||||||
(0, 5));
|
);
|
||||||
assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 11),
|
assert_eq!(
|
||||||
(0, 11));
|
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),
|
assert_eq!(
|
||||||
(1, 0));
|
f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 12),
|
||||||
assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 15),
|
(1, 0)
|
||||||
(1, 3));
|
);
|
||||||
assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 23),
|
assert_eq!(
|
||||||
(1, 11));
|
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),
|
assert_eq!(
|
||||||
(2, 0));
|
f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 24),
|
||||||
assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 28),
|
(2, 0)
|
||||||
(2, 4));
|
);
|
||||||
assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 35),
|
assert_eq!(
|
||||||
(2, 11));
|
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),
|
assert_eq!(
|
||||||
(3, 0));
|
f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 36),
|
||||||
assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 43),
|
(3, 0)
|
||||||
(3, 7));
|
);
|
||||||
assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 47),
|
assert_eq!(
|
||||||
(3, 11));
|
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),
|
assert_eq!(
|
||||||
(4, 0));
|
f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 48),
|
||||||
assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 50),
|
(4, 0)
|
||||||
(4, 2));
|
);
|
||||||
assert_eq!(f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 56),
|
assert_eq!(
|
||||||
(4, 8));
|
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),
|
assert_eq!(
|
||||||
(4, 8));
|
f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 57),
|
||||||
|
(4, 8)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn v2d_to_index_1() {
|
fn v2d_to_index_1() {
|
||||||
let text = "Hello there, stranger!"; // 22 graphemes long
|
let text = "Hello there, stranger!"; // 22 graphemes long
|
||||||
|
@ -425,33 +473,56 @@ mod tests {
|
||||||
f.wrap_additional_indent = 0;
|
f.wrap_additional_indent = 0;
|
||||||
f.set_wrap_width(80);
|
f.set_wrap_width(80);
|
||||||
|
|
||||||
assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true),
|
assert_eq!(
|
||||||
(0, 0),
|
f.v2d_to_index(
|
||||||
(Floor, Floor)),
|
UnicodeSegmentation::graphemes(text, true),
|
||||||
0);
|
(0, 0),
|
||||||
assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true),
|
(Floor, Floor)
|
||||||
(0, 5),
|
),
|
||||||
(Floor, Floor)),
|
0
|
||||||
5);
|
);
|
||||||
assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true),
|
assert_eq!(
|
||||||
(0, 22),
|
f.v2d_to_index(
|
||||||
(Floor, Floor)),
|
UnicodeSegmentation::graphemes(text, true),
|
||||||
22);
|
(0, 5),
|
||||||
assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true),
|
(Floor, Floor)
|
||||||
(0, 23),
|
),
|
||||||
(Floor, Floor)),
|
5
|
||||||
22);
|
);
|
||||||
assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true),
|
assert_eq!(
|
||||||
(1, 0),
|
f.v2d_to_index(
|
||||||
(Floor, Floor)),
|
UnicodeSegmentation::graphemes(text, true),
|
||||||
22);
|
(0, 22),
|
||||||
assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true),
|
(Floor, Floor)
|
||||||
(1, 1),
|
),
|
||||||
(Floor, Floor)),
|
22
|
||||||
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]
|
#[test]
|
||||||
fn v2d_to_index_2() {
|
fn v2d_to_index_2() {
|
||||||
let text = "Hello there, stranger! How are you doing this fine day?"; // 56 graphemes long
|
let text = "Hello there, stranger! How are you doing this fine day?"; // 56 graphemes long
|
||||||
|
@ -462,77 +533,140 @@ mod tests {
|
||||||
f.wrap_additional_indent = 0;
|
f.wrap_additional_indent = 0;
|
||||||
f.set_wrap_width(12);
|
f.set_wrap_width(12);
|
||||||
|
|
||||||
assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true),
|
assert_eq!(
|
||||||
(0, 0),
|
f.v2d_to_index(
|
||||||
(Floor, Floor)),
|
UnicodeSegmentation::graphemes(text, true),
|
||||||
0);
|
(0, 0),
|
||||||
assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true),
|
(Floor, Floor)
|
||||||
(0, 11),
|
),
|
||||||
(Floor, Floor)),
|
0
|
||||||
11);
|
);
|
||||||
assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true),
|
assert_eq!(
|
||||||
(0, 12),
|
f.v2d_to_index(
|
||||||
(Floor, Floor)),
|
UnicodeSegmentation::graphemes(text, true),
|
||||||
11);
|
(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),
|
assert_eq!(
|
||||||
(1, 0),
|
f.v2d_to_index(
|
||||||
(Floor, Floor)),
|
UnicodeSegmentation::graphemes(text, true),
|
||||||
12);
|
(1, 0),
|
||||||
assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true),
|
(Floor, Floor)
|
||||||
(1, 11),
|
),
|
||||||
(Floor, Floor)),
|
12
|
||||||
23);
|
);
|
||||||
assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true),
|
assert_eq!(
|
||||||
(1, 12),
|
f.v2d_to_index(
|
||||||
(Floor, Floor)),
|
UnicodeSegmentation::graphemes(text, true),
|
||||||
23);
|
(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),
|
assert_eq!(
|
||||||
(2, 0),
|
f.v2d_to_index(
|
||||||
(Floor, Floor)),
|
UnicodeSegmentation::graphemes(text, true),
|
||||||
24);
|
(2, 0),
|
||||||
assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true),
|
(Floor, Floor)
|
||||||
(2, 11),
|
),
|
||||||
(Floor, Floor)),
|
24
|
||||||
35);
|
);
|
||||||
assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true),
|
assert_eq!(
|
||||||
(2, 12),
|
f.v2d_to_index(
|
||||||
(Floor, Floor)),
|
UnicodeSegmentation::graphemes(text, true),
|
||||||
35);
|
(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),
|
assert_eq!(
|
||||||
(3, 0),
|
f.v2d_to_index(
|
||||||
(Floor, Floor)),
|
UnicodeSegmentation::graphemes(text, true),
|
||||||
36);
|
(3, 0),
|
||||||
assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true),
|
(Floor, Floor)
|
||||||
(3, 11),
|
),
|
||||||
(Floor, Floor)),
|
36
|
||||||
47);
|
);
|
||||||
assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true),
|
assert_eq!(
|
||||||
(3, 12),
|
f.v2d_to_index(
|
||||||
(Floor, Floor)),
|
UnicodeSegmentation::graphemes(text, true),
|
||||||
47);
|
(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),
|
assert_eq!(
|
||||||
(4, 0),
|
f.v2d_to_index(
|
||||||
(Floor, Floor)),
|
UnicodeSegmentation::graphemes(text, true),
|
||||||
48);
|
(4, 0),
|
||||||
assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true),
|
(Floor, Floor)
|
||||||
(4, 7),
|
),
|
||||||
(Floor, Floor)),
|
48
|
||||||
55);
|
);
|
||||||
assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true),
|
assert_eq!(
|
||||||
(4, 8),
|
f.v2d_to_index(
|
||||||
(Floor, Floor)),
|
UnicodeSegmentation::graphemes(text, true),
|
||||||
56);
|
(4, 7),
|
||||||
assert_eq!(f.v2d_to_index(UnicodeSegmentation::graphemes(text, true),
|
(Floor, Floor)
|
||||||
(4, 9),
|
),
|
||||||
(Floor, Floor)),
|
55
|
||||||
56);
|
);
|
||||||
|
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]
|
#[test]
|
||||||
fn index_to_horizontal_v2d_1() {
|
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 b = Buffer::new_from_str("Hello there, stranger!\nHow are you doing this fine day?"); // 55 graphemes long
|
||||||
|
@ -550,7 +684,6 @@ mod tests {
|
||||||
assert_eq!(f.index_to_horizontal_v2d(&b, 56), 32);
|
assert_eq!(f.index_to_horizontal_v2d(&b, 56), 32);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn index_to_horizontal_v2d_2() {
|
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 b = Buffer::new_from_str("Hello there, stranger!\nHow are you doing this fine day?"); // 55 graphemes long
|
||||||
|
@ -578,7 +711,6 @@ mod tests {
|
||||||
assert_eq!(f.index_to_horizontal_v2d(&b, 56), 8);
|
assert_eq!(f.index_to_horizontal_v2d(&b, 56), 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn index_set_horizontal_v2d_1() {
|
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 b = Buffer::new_from_str("Hello there, stranger!\nHow are you doing this fine day?"); // 55 graphemes long
|
||||||
|
@ -614,7 +746,6 @@ mod tests {
|
||||||
assert_eq!(f.index_set_horizontal_v2d(&b, 55, 33, Floor), 55);
|
assert_eq!(f.index_set_horizontal_v2d(&b, 55, 33, Floor), 55);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn index_set_horizontal_v2d_2() {
|
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 b = Buffer::new_from_str("Hello there, stranger! How are you doing this fine day?"); // 55 graphemes long
|
||||||
|
@ -650,7 +781,6 @@ mod tests {
|
||||||
assert_eq!(f.index_set_horizontal_v2d(&b, 23, 12, Floor), 23);
|
assert_eq!(f.index_set_horizontal_v2d(&b, 23, 12, Floor), 23);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn index_offset_vertical_v2d_1() {
|
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 b = Buffer::new_from_str("Hello there, stranger!\nHow are you doing this fine day?"); // 55 graphemes long
|
||||||
|
@ -678,7 +808,6 @@ mod tests {
|
||||||
assert_eq!(f.index_offset_vertical_v2d(&b, 54, -1, (Floor, Floor)), 22);
|
assert_eq!(f.index_offset_vertical_v2d(&b, 54, -1, (Floor, Floor)), 22);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn index_offset_vertical_v2d_2() {
|
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 b = Buffer::new_from_str("Hello there, stranger! How are you doing this fine day?"); // 55 graphemes long
|
||||||
|
|
|
@ -4,7 +4,7 @@ use rustbox;
|
||||||
use rustbox::Color;
|
use rustbox::Color;
|
||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use formatter::{LineFormatter, LINE_BLOCK_LENGTH, block_index_and_offset};
|
use formatter::{block_index_and_offset, LineFormatter, LINE_BLOCK_LENGTH};
|
||||||
use std::char;
|
use std::char;
|
||||||
use std::default::Default;
|
use std::default::Default;
|
||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
|
@ -34,7 +34,6 @@ const K_CTRL_S: u16 = 19;
|
||||||
const K_CTRL_Y: u16 = 25;
|
const K_CTRL_Y: u16 = 25;
|
||||||
const K_CTRL_Z: u16 = 26;
|
const K_CTRL_Z: u16 = 26;
|
||||||
|
|
||||||
|
|
||||||
pub struct TermUI {
|
pub struct TermUI {
|
||||||
rb: rustbox::RustBox,
|
rb: rustbox::RustBox,
|
||||||
editor: Editor<ConsoleLineFormatter>,
|
editor: Editor<ConsoleLineFormatter>,
|
||||||
|
@ -42,7 +41,6 @@ pub struct TermUI {
|
||||||
height: usize,
|
height: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl TermUI {
|
impl TermUI {
|
||||||
pub fn new() -> TermUI {
|
pub fn new() -> TermUI {
|
||||||
let rb = match rustbox::RustBox::init(rustbox::InitOptions {
|
let rb = match rustbox::RustBox::init(rustbox::InitOptions {
|
||||||
|
@ -102,7 +100,6 @@ impl TermUI {
|
||||||
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();
|
self.rb.present();
|
||||||
|
|
||||||
|
|
||||||
// Handle events. We block on the first event, so that the
|
// Handle events. We block on the first event, so that the
|
||||||
// program doesn't loop like crazy, but then continue pulling
|
// program doesn't loop like crazy, but then continue pulling
|
||||||
// events in a non-blocking way until we run out of events
|
// events in a non-blocking way until we run out of events
|
||||||
|
@ -216,7 +213,6 @@ impl TermUI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn go_to_line_ui_loop(&mut self) {
|
fn go_to_line_ui_loop(&mut self) {
|
||||||
let foreground = Color::Black;
|
let foreground = Color::Black;
|
||||||
let background = Color::Cyan;
|
let background = Color::Cyan;
|
||||||
|
@ -233,18 +229,21 @@ impl TermUI {
|
||||||
self.rb.clear();
|
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 {
|
for i in 0..self.width {
|
||||||
self.rb.print(i, 0, rustbox::RB_NORMAL, foreground, background, " ");
|
self.rb
|
||||||
|
.print(i, 0, rustbox::RB_NORMAL, foreground, background, " ");
|
||||||
}
|
}
|
||||||
self.rb.print(1, 0, rustbox::RB_NORMAL, foreground, background, prefix);
|
self.rb
|
||||||
self.rb.print(prefix.len() + 1,
|
.print(1, 0, rustbox::RB_NORMAL, foreground, background, prefix);
|
||||||
0,
|
self.rb.print(
|
||||||
rustbox::RB_NORMAL,
|
prefix.len() + 1,
|
||||||
foreground,
|
0,
|
||||||
background,
|
rustbox::RB_NORMAL,
|
||||||
&line[..]);
|
foreground,
|
||||||
|
background,
|
||||||
|
&line[..],
|
||||||
|
);
|
||||||
self.rb.present();
|
self.rb.present();
|
||||||
|
|
||||||
|
|
||||||
// Handle events. We block on the first event, so that the
|
// Handle events. We block on the first event, so that the
|
||||||
// program doesn't loop like crazy, but then continue pulling
|
// program doesn't loop like crazy, but then continue pulling
|
||||||
// events in a non-blocking way until we run out of events
|
// events in a non-blocking way until we run out of events
|
||||||
|
@ -295,7 +294,6 @@ impl TermUI {
|
||||||
e = self.rb.peek_event(Duration::from_millis(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
|
// Cancel if flag is set
|
||||||
if cancel {
|
if cancel {
|
||||||
break;
|
break;
|
||||||
|
@ -316,50 +314,52 @@ impl TermUI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn draw_editor(
|
||||||
fn draw_editor(&self,
|
&self,
|
||||||
editor: &Editor<ConsoleLineFormatter>,
|
editor: &Editor<ConsoleLineFormatter>,
|
||||||
c1: (usize, usize),
|
c1: (usize, usize),
|
||||||
c2: (usize, usize)) {
|
c2: (usize, usize),
|
||||||
|
) {
|
||||||
let foreground = Color::Black;
|
let foreground = Color::Black;
|
||||||
let background = Color::Cyan;
|
let background = Color::Cyan;
|
||||||
|
|
||||||
// Fill in top row with info line color
|
// Fill in top row with info line color
|
||||||
for i in c1.1..(c2.1 + 1) {
|
for i in c1.1..(c2.1 + 1) {
|
||||||
self.rb.print(i, c1.0, rustbox::RB_NORMAL, foreground, background, " ");
|
self.rb
|
||||||
|
.print(i, c1.0, rustbox::RB_NORMAL, foreground, background, " ");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filename and dirty marker
|
// Filename and dirty marker
|
||||||
let filename = editor.file_path.display();
|
let filename = editor.file_path.display();
|
||||||
let dirty_char = if editor.dirty {
|
let dirty_char = if editor.dirty { "*" } else { "" };
|
||||||
"*"
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
};
|
|
||||||
let name = format!("{}{}", filename, dirty_char);
|
let name = format!("{}{}", filename, dirty_char);
|
||||||
self.rb.print(c1.1 + 1,
|
self.rb.print(
|
||||||
c1.0,
|
c1.1 + 1,
|
||||||
rustbox::RB_NORMAL,
|
c1.0,
|
||||||
foreground,
|
rustbox::RB_NORMAL,
|
||||||
background,
|
foreground,
|
||||||
&name[..]);
|
background,
|
||||||
|
&name[..],
|
||||||
|
);
|
||||||
|
|
||||||
// Percentage position in document
|
// Percentage position in document
|
||||||
// TODO: use view instead of cursor for calculation if there is more
|
// TODO: use view instead of cursor for calculation if there is more
|
||||||
// than one cursor.
|
// than one cursor.
|
||||||
let percentage: usize = if editor.buffer.grapheme_count() > 0 {
|
let percentage: usize = if editor.buffer.grapheme_count() > 0 {
|
||||||
(((editor.cursors[0].range.0 as f32) / (editor.buffer.grapheme_count() as f32)) *
|
(((editor.cursors[0].range.0 as f32) / (editor.buffer.grapheme_count() as f32)) * 100.0)
|
||||||
100.0) as usize
|
as usize
|
||||||
} else {
|
} else {
|
||||||
100
|
100
|
||||||
};
|
};
|
||||||
let pstring = format!("{}%", percentage);
|
let pstring = format!("{}%", percentage);
|
||||||
self.rb.print(c2.1 - pstring.len(),
|
self.rb.print(
|
||||||
c1.0,
|
c2.1 - pstring.len(),
|
||||||
rustbox::RB_NORMAL,
|
c1.0,
|
||||||
foreground,
|
rustbox::RB_NORMAL,
|
||||||
background,
|
foreground,
|
||||||
&pstring[..]);
|
background,
|
||||||
|
&pstring[..],
|
||||||
|
);
|
||||||
|
|
||||||
// Text encoding info and tab style
|
// Text encoding info and tab style
|
||||||
let nl = match editor.line_ending_type {
|
let nl = match editor.line_ending_type {
|
||||||
|
@ -373,40 +373,48 @@ impl TermUI {
|
||||||
LineEnding::LS => "LS",
|
LineEnding::LS => "LS",
|
||||||
LineEnding::PS => "PS",
|
LineEnding::PS => "PS",
|
||||||
};
|
};
|
||||||
let soft_tabs_str = if editor.soft_tabs {
|
let soft_tabs_str = if editor.soft_tabs { "spaces" } else { "tabs" };
|
||||||
"spaces"
|
let info_line = format!(
|
||||||
} else {
|
"UTF8:{} {}:{}",
|
||||||
"tabs"
|
nl, soft_tabs_str, editor.soft_tab_width as usize
|
||||||
};
|
);
|
||||||
let info_line = format!("UTF8:{} {}:{}",
|
self.rb.print(
|
||||||
nl,
|
c2.1 - 30,
|
||||||
soft_tabs_str,
|
c1.0,
|
||||||
editor.soft_tab_width as usize);
|
rustbox::RB_NORMAL,
|
||||||
self.rb.print(c2.1 - 30,
|
foreground,
|
||||||
c1.0,
|
background,
|
||||||
rustbox::RB_NORMAL,
|
&info_line[..],
|
||||||
foreground,
|
);
|
||||||
background,
|
|
||||||
&info_line[..]);
|
|
||||||
|
|
||||||
// Draw main text editing area
|
// Draw main text editing area
|
||||||
self.draw_editor_text(editor, (c1.0 + 1, c1.1), c2);
|
self.draw_editor_text(editor, (c1.0 + 1, c1.1), c2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn draw_editor_text(
|
||||||
fn draw_editor_text(&self,
|
&self,
|
||||||
editor: &Editor<ConsoleLineFormatter>,
|
editor: &Editor<ConsoleLineFormatter>,
|
||||||
c1: (usize, usize),
|
c1: (usize, usize),
|
||||||
c2: (usize, usize)) {
|
c2: (usize, usize),
|
||||||
|
) {
|
||||||
// Calculate all the starting info
|
// Calculate all the starting info
|
||||||
let gutter_width = editor.editor_dim.1 - editor.view_dim.1;
|
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 (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 line_block_index, _) = block_index_and_offset(col_i);
|
||||||
let mut grapheme_index = editor.buffer.line_col_to_index((line_index,
|
let mut grapheme_index = editor
|
||||||
line_block_index *
|
.buffer
|
||||||
LINE_BLOCK_LENGTH));
|
.line_col_to_index((line_index, line_block_index * LINE_BLOCK_LENGTH));
|
||||||
let temp_line = editor.buffer.get_line(line_index);
|
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 (vis_line_offset, _) = editor.formatter.index_to_v2d(
|
||||||
|
temp_line.slice(
|
||||||
|
line_block_index * LINE_BLOCK_LENGTH,
|
||||||
|
min(
|
||||||
|
temp_line.len_chars(),
|
||||||
|
(line_block_index + 1) * LINE_BLOCK_LENGTH,
|
||||||
|
),
|
||||||
|
).graphemes(),
|
||||||
|
editor.view_pos.0 - grapheme_index,
|
||||||
|
);
|
||||||
|
|
||||||
let mut screen_line = c1.0 as isize - vis_line_offset as isize;
|
let mut screen_line = c1.0 as isize - vis_line_offset as isize;
|
||||||
let screen_col = c1.1 as isize + gutter_width as isize;
|
let screen_col = c1.1 as isize + gutter_width as isize;
|
||||||
|
@ -414,7 +422,8 @@ impl TermUI {
|
||||||
// Fill in the gutter with the appropriate background
|
// Fill in the gutter with the appropriate background
|
||||||
for y in c1.0..(c2.0 + 1) {
|
for y in c1.0..(c2.0 + 1) {
|
||||||
for x in c1.1..(c1.1 + gutter_width - 1) {
|
for x in c1.1..(c1.1 + gutter_width - 1) {
|
||||||
self.rb.print(x, y, rustbox::RB_NORMAL, Color::White, Color::Blue, " ");
|
self.rb
|
||||||
|
.print(x, y, rustbox::RB_NORMAL, Color::White, Color::Blue, " ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -425,12 +434,14 @@ impl TermUI {
|
||||||
let lnx = c1.1 + (gutter_width - 1 - digit_count(line_num as u32, 10) as usize);
|
let lnx = c1.1 + (gutter_width - 1 - digit_count(line_num as u32, 10) as usize);
|
||||||
let lny = screen_line as usize;
|
let lny = screen_line as usize;
|
||||||
if lny >= c1.0 && lny <= c2.0 {
|
if lny >= c1.0 && lny <= c2.0 {
|
||||||
self.rb.print(lnx,
|
self.rb.print(
|
||||||
lny,
|
lnx,
|
||||||
rustbox::RB_NORMAL,
|
lny,
|
||||||
Color::White,
|
rustbox::RB_NORMAL,
|
||||||
Color::Blue,
|
Color::White,
|
||||||
&format!("{}", line_num)[..]);
|
Color::Blue,
|
||||||
|
&format!("{}", line_num)[..],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -439,8 +450,10 @@ impl TermUI {
|
||||||
let mut line_g_index: usize = 0;
|
let mut line_g_index: usize = 0;
|
||||||
let mut last_pos_y = 0;
|
let mut last_pos_y = 0;
|
||||||
let mut lines_traversed: usize = 0;
|
let mut lines_traversed: usize = 0;
|
||||||
let mut g_iter = editor.formatter.iter(line.grapheme_iter_at_index(line_block_index *
|
let line_len = line.len_chars();
|
||||||
LINE_BLOCK_LENGTH));
|
let mut g_iter = editor
|
||||||
|
.formatter
|
||||||
|
.iter(line.slice(line_block_index * LINE_BLOCK_LENGTH, line_len).graphemes());
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if let Some((g, (pos_y, pos_x), width)) = g_iter.next() {
|
if let Some((g, (pos_y, pos_x), width)) = g_iter.next() {
|
||||||
|
@ -472,49 +485,59 @@ impl TermUI {
|
||||||
// Actually print the character
|
// Actually print the character
|
||||||
if is_line_ending(g) {
|
if is_line_ending(g) {
|
||||||
if at_cursor {
|
if at_cursor {
|
||||||
self.rb.print(px as usize,
|
self.rb.print(
|
||||||
py as usize,
|
px as usize,
|
||||||
rustbox::RB_NORMAL,
|
py as usize,
|
||||||
Color::Black,
|
rustbox::RB_NORMAL,
|
||||||
Color::White,
|
Color::Black,
|
||||||
" ");
|
Color::White,
|
||||||
|
" ",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else if g == "\t" {
|
} else if g == "\t" {
|
||||||
for i in 0..width {
|
for i in 0..width {
|
||||||
let tpx = px as usize + i;
|
let tpx = px as usize + i;
|
||||||
if tpx <= c2.1 {
|
if tpx <= c2.1 {
|
||||||
self.rb.print(tpx as usize,
|
self.rb.print(
|
||||||
py as usize,
|
tpx as usize,
|
||||||
rustbox::RB_NORMAL,
|
py as usize,
|
||||||
Color::White,
|
rustbox::RB_NORMAL,
|
||||||
Color::Black,
|
Color::White,
|
||||||
" ");
|
Color::Black,
|
||||||
|
" ",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if at_cursor {
|
if at_cursor {
|
||||||
self.rb.print(px as usize,
|
self.rb.print(
|
||||||
py as usize,
|
px as usize,
|
||||||
rustbox::RB_NORMAL,
|
py as usize,
|
||||||
Color::Black,
|
rustbox::RB_NORMAL,
|
||||||
Color::White,
|
Color::Black,
|
||||||
" ");
|
Color::White,
|
||||||
|
" ",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if at_cursor {
|
if at_cursor {
|
||||||
self.rb.print(px as usize,
|
self.rb.print(
|
||||||
py as usize,
|
px as usize,
|
||||||
rustbox::RB_NORMAL,
|
py as usize,
|
||||||
Color::Black,
|
rustbox::RB_NORMAL,
|
||||||
Color::White,
|
Color::Black,
|
||||||
g);
|
Color::White,
|
||||||
|
g,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
self.rb.print(px as usize,
|
self.rb.print(
|
||||||
py as usize,
|
px as usize,
|
||||||
rustbox::RB_NORMAL,
|
py as usize,
|
||||||
Color::White,
|
rustbox::RB_NORMAL,
|
||||||
Color::Black,
|
Color::White,
|
||||||
g);
|
Color::Black,
|
||||||
|
g,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -528,8 +551,10 @@ impl TermUI {
|
||||||
if line_g_index >= LINE_BLOCK_LENGTH {
|
if line_g_index >= LINE_BLOCK_LENGTH {
|
||||||
line_block_index += 1;
|
line_block_index += 1;
|
||||||
line_g_index = 0;
|
line_g_index = 0;
|
||||||
g_iter = editor.formatter.iter(line.grapheme_iter_at_index(line_block_index *
|
let line_len = line.len_chars();
|
||||||
LINE_BLOCK_LENGTH));
|
g_iter = editor
|
||||||
|
.formatter
|
||||||
|
.iter(line.slice(line_block_index * LINE_BLOCK_LENGTH, line_len).graphemes());
|
||||||
lines_traversed += 1;
|
lines_traversed += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -539,7 +564,6 @@ impl TermUI {
|
||||||
line_num += 1;
|
line_num += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// If we get here, it means we reached the end of the text buffer
|
// 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
|
// without going off the bottom of the screen. So draw the cursor
|
||||||
// at the end if needed.
|
// at the end if needed.
|
||||||
|
@ -554,21 +578,23 @@ impl TermUI {
|
||||||
|
|
||||||
if at_cursor {
|
if at_cursor {
|
||||||
// Calculate the cell coordinates at which to draw the cursor
|
// Calculate the cell coordinates at which to draw the cursor
|
||||||
let pos_x = editor.formatter.index_to_horizontal_v2d(&self.editor.buffer,
|
let pos_x = editor
|
||||||
self.editor
|
.formatter
|
||||||
.buffer
|
.index_to_horizontal_v2d(&self.editor.buffer, self.editor.buffer.grapheme_count());
|
||||||
.grapheme_count());
|
|
||||||
let px = pos_x as isize + screen_col - editor.view_pos.1 as isize;
|
let px = pos_x as isize + screen_col - editor.view_pos.1 as isize;
|
||||||
let py = screen_line - 1;
|
let py = screen_line - 1;
|
||||||
|
|
||||||
if (px >= c1.1 as isize) && (py >= c1.0 as isize) && (px <= c2.1 as isize) &&
|
if (px >= c1.1 as isize) && (py >= c1.0 as isize) && (px <= c2.1 as isize)
|
||||||
(py <= c2.0 as isize) {
|
&& (py <= c2.0 as isize)
|
||||||
self.rb.print(px as usize,
|
{
|
||||||
py as usize,
|
self.rb.print(
|
||||||
rustbox::RB_NORMAL,
|
px as usize,
|
||||||
Color::Black,
|
py as usize,
|
||||||
Color::White,
|
rustbox::RB_NORMAL,
|
||||||
" ");
|
Color::Black,
|
||||||
|
Color::White,
|
||||||
|
" ",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,6 @@ pub fn digit_count(mut n: u32, b: u32) -> u32 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "ropey"
|
|
||||||
version = "0.3.0"
|
|
||||||
authors = ["Nathan Vegdahl <cessen@cessen.com>"]
|
|
||||||
license = "MIT"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
unicode-segmentation = "1.2"
|
|
|
@ -1,266 +0,0 @@
|
||||||
#![cfg(test)]
|
|
||||||
/*
|
|
||||||
use super::*;
|
|
||||||
use test::Bencher;
|
|
||||||
|
|
||||||
|
|
||||||
#[bench]
|
|
||||||
fn new_from_str_1(b: &mut Bencher) {
|
|
||||||
b.iter(|| {
|
|
||||||
let _ = Rope::from_str("
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[bench]
|
|
||||||
fn new_from_str_2(b: &mut Bencher) {
|
|
||||||
b.iter(|| {
|
|
||||||
let _ = Rope::from_str("
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[bench]
|
|
||||||
fn new_from_str_3(b: &mut Bencher) {
|
|
||||||
b.iter(|| {
|
|
||||||
let _ = Rope::from_str("
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[bench]
|
|
||||||
fn new_from_str_4(b: &mut Bencher) {
|
|
||||||
b.iter(|| {
|
|
||||||
let _ = Rope::from_str("
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[bench]
|
|
||||||
fn insert_text_bench_1(b: &mut Bencher) {
|
|
||||||
b.iter(|| {
|
|
||||||
let mut rope = Rope::new();
|
|
||||||
for _ in 0..200 {
|
|
||||||
rope.insert_text_at_char_index("Hi", 0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[bench]
|
|
||||||
fn insert_text_bench_2(b: &mut Bencher) {
|
|
||||||
b.iter(|| {
|
|
||||||
let mut rope = Rope::new();
|
|
||||||
for i in 0..200 {
|
|
||||||
rope.insert_text_at_char_index("Hi", i/2);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[bench]
|
|
||||||
fn insert_text_bench_3(b: &mut Bencher) {
|
|
||||||
b.iter(|| {
|
|
||||||
let mut rope = Rope::new();
|
|
||||||
for i in 0..200 {
|
|
||||||
rope.insert_text_at_char_index("Hi", i);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[bench]
|
|
||||||
fn insert_large_text_bench_1(b: &mut Bencher) {
|
|
||||||
let s = String::from_utf8(vec!['c' as u8; 3457]).unwrap();
|
|
||||||
b.iter(|| {
|
|
||||||
let mut rope = Rope::from_str("Hello there!");
|
|
||||||
rope.insert_text_at_char_index(&s[..], 0);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[bench]
|
|
||||||
fn insert_large_text_bench_2(b: &mut Bencher) {
|
|
||||||
let s = String::from_utf8(vec!['c' as u8; 3457]).unwrap();
|
|
||||||
b.iter(|| {
|
|
||||||
let mut rope = Rope::from_str("Hello there!");
|
|
||||||
rope.insert_text_at_char_index(&s[..], 3);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[bench]
|
|
||||||
fn insert_large_text_bench_3(b: &mut Bencher) {
|
|
||||||
let s = String::from_utf8(vec!['c' as u8; 3457]).unwrap();
|
|
||||||
b.iter(|| {
|
|
||||||
let mut rope = Rope::from_str("Hello there!");
|
|
||||||
rope.insert_text_at_char_index(&s[..], 12);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[bench]
|
|
||||||
fn remove_text_bench_1(b: &mut Bencher) {
|
|
||||||
b.iter(|| {
|
|
||||||
let mut rope = Rope::from_str("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
|
|
||||||
for _ in 0..200 {
|
|
||||||
rope.remove_text_between_char_indices(0, 2);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[bench]
|
|
||||||
fn remove_text_bench_2(b: &mut Bencher) {
|
|
||||||
b.iter(|| {
|
|
||||||
let mut rope = Rope::from_str("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
|
|
||||||
for i in 0..200 {
|
|
||||||
rope.remove_text_between_char_indices((200-i)-1, (200-i)+1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[bench]
|
|
||||||
fn remove_text_bench_3(b: &mut Bencher) {
|
|
||||||
b.iter(|| {
|
|
||||||
let mut rope = Rope::from_str("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
|
|
||||||
for i in 0..200 {
|
|
||||||
rope.remove_text_between_char_indices(400-(i*2)-2, 400-(i*2));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[bench]
|
|
||||||
fn append_1(b: &mut Bencher) {
|
|
||||||
b.iter(|| {
|
|
||||||
let mut left = Rope::from_str(&(String::from_utf8(vec!['c' as u8; 3617]).unwrap())[..]);
|
|
||||||
let right = Rope::from_str(&(String::from_utf8(vec!['c' as u8; 3617]).unwrap())[..]);
|
|
||||||
left.append(right);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[bench]
|
|
||||||
fn append_2(b: &mut Bencher) {
|
|
||||||
b.iter(|| {
|
|
||||||
let mut left = Rope::from_str(&(String::from_utf8(vec!['c' as u8; 263]).unwrap())[..]);
|
|
||||||
let right = Rope::from_str(&(String::from_utf8(vec!['c' as u8; 3617]).unwrap())[..]);
|
|
||||||
left.append(right);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[bench]
|
|
||||||
fn append_3(b: &mut Bencher) {
|
|
||||||
b.iter(|| {
|
|
||||||
let mut left = Rope::from_str(&(String::from_utf8(vec!['c' as u8; 3617]).unwrap())[..]);
|
|
||||||
let right = Rope::from_str(&(String::from_utf8(vec!['c' as u8; 263]).unwrap())[..]);
|
|
||||||
left.append(right);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[bench]
|
|
||||||
fn split_at_char_index_1(b: &mut Bencher) {
|
|
||||||
b.iter(|| {
|
|
||||||
let mut left = Rope::from_str(&(String::from_utf8(vec!['c' as u8; 7649]).unwrap())[..]);
|
|
||||||
let _ = left.split_at_char_index(3617);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[bench]
|
|
||||||
fn split_at_char_index_2(b: &mut Bencher) {
|
|
||||||
b.iter(|| {
|
|
||||||
let mut left = Rope::from_str(&(String::from_utf8(vec!['c' as u8; 7649]).unwrap())[..]);
|
|
||||||
let _ = left.split_at_char_index(263);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
*/
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,481 +0,0 @@
|
||||||
#![allow(dead_code)]
|
|
||||||
//! Misc helpful utility functions for TextBuffer related stuff.
|
|
||||||
|
|
||||||
use std::str::CharIndices;
|
|
||||||
use std::iter::repeat;
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn line_ending_count(text: &str) -> usize {
|
|
||||||
let mut count = 0;
|
|
||||||
for g in UnicodeSegmentation::graphemes(text, true) {
|
|
||||||
if is_line_ending(g) {
|
|
||||||
count += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn char_count(text: &str) -> usize {
|
|
||||||
let mut count = 0;
|
|
||||||
for _ in text.chars() {
|
|
||||||
count += 1;
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn grapheme_count(text: &str) -> usize {
|
|
||||||
let mut count = 0;
|
|
||||||
for _ in UnicodeSegmentation::graphemes(text, true) {
|
|
||||||
count += 1;
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn grapheme_count_is_less_than(text: &str, n: usize) -> bool {
|
|
||||||
let mut count = 0;
|
|
||||||
for _ in UnicodeSegmentation::graphemes(text, true) {
|
|
||||||
count += 1;
|
|
||||||
if count >= n {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn char_grapheme_line_ending_count(text: &str) -> (usize, usize, usize) {
|
|
||||||
let mut cc = 0;
|
|
||||||
let mut gc = 0;
|
|
||||||
let mut lec = 0;
|
|
||||||
|
|
||||||
for g in UnicodeSegmentation::graphemes(text, true) {
|
|
||||||
cc += char_count(g);
|
|
||||||
gc += 1;
|
|
||||||
if is_line_ending(g) {
|
|
||||||
lec += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (cc, gc, lec);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn graphemes_are_mergeable(s1: &str, s2: &str) -> bool {
|
|
||||||
let mut s = String::with_capacity(s1.len() + s2.len());
|
|
||||||
s.push_str(s1);
|
|
||||||
s.push_str(s2);
|
|
||||||
|
|
||||||
return grapheme_count(&s[..]) == 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
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.");
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn char_pos_to_grapheme_pos(text: &str, pos: usize) -> usize {
|
|
||||||
let mut i = 0usize;
|
|
||||||
let mut cc = 0usize;
|
|
||||||
|
|
||||||
for g in UnicodeSegmentation::graphemes(text, true) {
|
|
||||||
if cc == pos {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
|
|
||||||
cc += char_count(g);
|
|
||||||
|
|
||||||
if cc > pos {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if cc == pos {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
|
|
||||||
panic!("char_pos_to_grapheme_pos(): char position off the end of the string.");
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn grapheme_pos_to_char_pos(text: &str, pos: usize) -> usize {
|
|
||||||
let mut i = 0usize;
|
|
||||||
let mut cc = 0usize;
|
|
||||||
|
|
||||||
for g in UnicodeSegmentation::graphemes(text, true) {
|
|
||||||
if i == pos {
|
|
||||||
return cc;
|
|
||||||
}
|
|
||||||
|
|
||||||
cc += char_count(g);
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if i == pos {
|
|
||||||
return cc;
|
|
||||||
}
|
|
||||||
|
|
||||||
panic!("grapheme_pos_to_char_pos(): grapheme position off the end of the string.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Inserts the given text into the given string at the given grapheme index.
|
|
||||||
pub fn insert_text_at_char_index(s: &mut String, text: &str, pos: usize) {
|
|
||||||
// Find insertion position in bytes
|
|
||||||
let byte_pos = char_pos_to_byte_pos(&s[..], pos);
|
|
||||||
|
|
||||||
// Get byte vec of string
|
|
||||||
let byte_vec = unsafe { s.as_mut_vec() };
|
|
||||||
|
|
||||||
// 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();
|
|
||||||
let mut to = byte_vec.len();
|
|
||||||
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 b in text.bytes() {
|
|
||||||
byte_vec[i] = b;
|
|
||||||
i += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Inserts the given text into the given string at the given grapheme index.
|
|
||||||
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
|
|
||||||
byte_vec.extend(repeat(0).take(text.len()));
|
|
||||||
|
|
||||||
// Move old bytes forward
|
|
||||||
// TODO: use copy_memory()...?
|
|
||||||
let mut from = byte_vec.len() - text.len();
|
|
||||||
let mut to = byte_vec.len();
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Removes the text between the given char indices in the given string.
|
|
||||||
pub fn remove_text_between_char_indices(s: &mut String, pos_a: usize, pos_b: usize) {
|
|
||||||
// Bounds checks
|
|
||||||
assert!(pos_a <= pos_b, "remove_text_between_char_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 = char_pos_to_byte_pos(&s[..], pos_a);
|
|
||||||
let byte_pos_b = char_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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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.");
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Splits a string into two strings at the char index given.
|
|
||||||
/// The first section of the split is stored in the original string,
|
|
||||||
/// while the second section of the split is returned as a new string.
|
|
||||||
pub fn split_string_at_char_index(s1: &mut String, pos: usize) -> String {
|
|
||||||
let mut s2 = String::new();
|
|
||||||
|
|
||||||
// Code block to contain the borrow of s2
|
|
||||||
{
|
|
||||||
let byte_pos = char_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().map(|&i| i));
|
|
||||||
byte_vec_1.truncate(byte_pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
return s2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Splits a string into two strings at the grapheme index given.
|
|
||||||
/// The first section of the split is stored in the original string,
|
|
||||||
/// 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().map(|&i| i));
|
|
||||||
byte_vec_1.truncate(byte_pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
return s2;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// A grapheme iterator that only recognizes CRLF as a composite grapheme.
|
|
||||||
/// This is only temporary, a stand-in for the proper Graphemes iterator
|
|
||||||
/// from stdlib which is currently marked unstable and thus is unavailable
|
|
||||||
/// in stable Rust builds. When Graphemes makes its way back into stdlib
|
|
||||||
/// or is split into another library, replace this with it.
|
|
||||||
pub struct TempGraphemeIndices<'a> {
|
|
||||||
s: &'a str,
|
|
||||||
chars: CharIndices<'a>,
|
|
||||||
i1: usize,
|
|
||||||
i2: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> TempGraphemeIndices<'a> {
|
|
||||||
fn push_i2(&mut self) {
|
|
||||||
if let Some((i, _)) = self.chars.next() {
|
|
||||||
self.i2 = i;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
self.i2 = self.s.len();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Iterator for TempGraphemeIndices<'a> {
|
|
||||||
type Item = (usize, &'a str);
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<(usize, &'a str)> {
|
|
||||||
// Advance the iterator
|
|
||||||
if self.i1 == self.i2 {
|
|
||||||
self.push_i2();
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we're at the end, we're done
|
|
||||||
if self.i1 == self.s.len() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
match &(self.s)[self.i1 .. self.i2] {
|
|
||||||
"\u{000D}" => {
|
|
||||||
let ii = self.i1;
|
|
||||||
let i3 = self.i2;
|
|
||||||
self.push_i2();
|
|
||||||
if &(self.s)[self.i1 .. self.i2] == "\u{000D}\u{000A}" {
|
|
||||||
let s = &(self.s)[self.i1 .. self.i2];
|
|
||||||
self.i1 = self.i2;
|
|
||||||
return Some((ii, s));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
let ii = self.i1;
|
|
||||||
let s = &(self.s)[self.i1 .. i3];
|
|
||||||
self.i1 = i3;
|
|
||||||
return Some((ii, s));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_ => {
|
|
||||||
let ii = self.i1;
|
|
||||||
let s = &(self.s)[self.i1 .. self.i2];
|
|
||||||
self.i1 = self.i2;
|
|
||||||
return Some((ii, s));
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn graphemes_are_mergeable_1() {
|
|
||||||
assert!(graphemes_are_mergeable("\u{000D}", "\u{000A}"));
|
|
||||||
assert!(!graphemes_are_mergeable("\u{000A}", "\u{000D}"));
|
|
||||||
assert!(!graphemes_are_mergeable("a", "b"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn char_pos_to_grapheme_pos_1() {
|
|
||||||
let s = "Hello\u{000D}\u{000A}there!";
|
|
||||||
|
|
||||||
assert_eq!(char_pos_to_grapheme_pos(s, 0), 0);
|
|
||||||
assert_eq!(char_pos_to_grapheme_pos(s, 5), 5);
|
|
||||||
assert_eq!(char_pos_to_grapheme_pos(s, 6), 5);
|
|
||||||
assert_eq!(char_pos_to_grapheme_pos(s, 7), 6);
|
|
||||||
assert_eq!(char_pos_to_grapheme_pos(s, 13), 12);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn char_pos_to_grapheme_pos_2() {
|
|
||||||
let s = "a";
|
|
||||||
|
|
||||||
assert_eq!(char_pos_to_grapheme_pos(s, 0), 0);
|
|
||||||
assert_eq!(char_pos_to_grapheme_pos(s, 1), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn char_pos_to_grapheme_pos_3() {
|
|
||||||
let s = "\u{000D}\u{000A}";
|
|
||||||
|
|
||||||
assert_eq!(char_pos_to_grapheme_pos(s, 0), 0);
|
|
||||||
assert_eq!(char_pos_to_grapheme_pos(s, 1), 0);
|
|
||||||
assert_eq!(char_pos_to_grapheme_pos(s, 2), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn char_pos_to_grapheme_pos_4() {
|
|
||||||
let s = "";
|
|
||||||
|
|
||||||
assert_eq!(char_pos_to_grapheme_pos(s, 0), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn grapheme_pos_to_char_pos_1() {
|
|
||||||
let s = "Hello\u{000D}\u{000A}there!";
|
|
||||||
|
|
||||||
assert_eq!(grapheme_pos_to_char_pos(s, 0), 0);
|
|
||||||
assert_eq!(grapheme_pos_to_char_pos(s, 5), 5);
|
|
||||||
assert_eq!(grapheme_pos_to_char_pos(s, 6), 7);
|
|
||||||
assert_eq!(grapheme_pos_to_char_pos(s, 8), 9);
|
|
||||||
assert_eq!(grapheme_pos_to_char_pos(s, 12), 13);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn grapheme_pos_to_char_pos_2() {
|
|
||||||
let s = "a";
|
|
||||||
|
|
||||||
assert_eq!(grapheme_pos_to_char_pos(s, 0), 0);
|
|
||||||
assert_eq!(grapheme_pos_to_char_pos(s, 1), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn grapheme_pos_to_char_pos_3() {
|
|
||||||
let s = "\u{000D}\u{000A}";
|
|
||||||
|
|
||||||
assert_eq!(grapheme_pos_to_char_pos(s, 0), 0);
|
|
||||||
assert_eq!(grapheme_pos_to_char_pos(s, 1), 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn grapheme_pos_to_char_pos_4() {
|
|
||||||
let s = "";
|
|
||||||
|
|
||||||
assert_eq!(grapheme_pos_to_char_pos(s, 0), 0);
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user