Updated to ropey git master, and got everything working again.

However, everything is very, very slow now.
This commit is contained in:
Nathan Vegdahl 2018-07-05 01:53:17 -07:00
parent 16f2aab223
commit de625e71dc
11 changed files with 883 additions and 628 deletions

9
Cargo.lock generated
View File

@ -3,7 +3,7 @@ name = "Led"
version = "0.0.2"
dependencies = [
"docopt 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)",
"ropey 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
"ropey 0.6.3 (git+https://github.com/cessen/ropey)",
"serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
"smallvec 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -87,11 +87,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "ropey"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
version = "0.6.3"
source = "git+https://github.com/cessen/ropey#e9b361b5b45d3e389bbfcb4f75127bec69e0b199"
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)",
]
[[package]]
@ -209,7 +208,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
"checksum regex 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "744554e01ccbd98fff8c457c3b092cd67af62a555a43bfe97ae8a0451f7799fa"
"checksum regex-syntax 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8e931c58b93d86f080c734bfd2bce7dd0079ae2331235818133c8be7f422e20e"
"checksum ropey 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "661796e716a1916c9fd80457ee204b05b47afa4c1f1ceec3c7fd40e59f0e31f1"
"checksum ropey 0.6.3 (git+https://github.com/cessen/ropey)" = "<none>"
"checksum serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)" = "db99f3919e20faa51bb2996057f5031d8685019b5a06139b1ce761da671b8526"
"checksum serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)" = "f4ba7591cfe93755e89eeecdbcc668885624829b020050e6aec99c2a03bd3fd0"
"checksum serde_derive_internals 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6e03f1c9530c3fb0a0a5c9b826bdd9246a5921ae995d75f512ac917fc4dd55b5"

View File

@ -9,7 +9,7 @@ name = "led"
path = "src/main.rs"
[dependencies]
ropey = "0.6"
ropey = { git = "https://github.com/cessen/ropey", branch = "master" }
unicode-segmentation = "1.2"
unicode-width = "0.1"
serde = "1.*"

File diff suppressed because it is too large Load Diff

View File

@ -7,8 +7,8 @@ use formatter::LineFormatter;
use formatter::RoundingBehavior::*;
use std::path::{Path, PathBuf};
use std::cmp::{max, min};
use string_utils::{char_count, str_to_line_ending, LineEnding};
use utils::digit_count;
use string_utils::{char_count, rope_slice_to_line_ending, LineEnding};
use utils::{digit_count, RopeGraphemes};
use self::cursor::CursorSet;
mod cursor;
@ -100,17 +100,15 @@ impl<T: LineFormatter> Editor<T> {
for line in self.buffer.line_iter().take(100) {
// Get the line ending
let ending = if line.len_chars() == 1 {
let g = line.slice((line.len_chars() - 1)..)
.graphemes()
let g = RopeGraphemes::new(&line.slice((line.len_chars() - 1)..))
.last()
.unwrap();
str_to_line_ending(g)
rope_slice_to_line_ending(&g)
} else if line.len_chars() > 1 {
let g = line.slice((line.len_chars() - 2)..)
.graphemes()
let g = RopeGraphemes::new(&line.slice((line.len_chars() - 2)..))
.last()
.unwrap();
str_to_line_ending(g)
rope_slice_to_line_ending(&g)
} else {
LineEnding::None
};
@ -626,7 +624,7 @@ impl<T: LineFormatter> Editor<T> {
pub fn page_up(&mut self) {
let move_amount =
self.view_dim.0 - max((self.view_dim.0 / 8), self.formatter.single_line_height());
self.view_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,
@ -642,7 +640,7 @@ impl<T: LineFormatter> Editor<T> {
pub fn page_down(&mut self) {
let move_amount =
self.view_dim.0 - max((self.view_dim.0 / 8), self.formatter.single_line_height());
self.view_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,

View File

@ -1,7 +1,9 @@
#![allow(dead_code)]
use std::cmp::min;
use ropey::RopeSlice;
use buffer::Buffer;
use utils::RopeGraphemes;
// Maximum graphemes in a line before a soft line break is forced.
// This is necessary to prevent pathological formatting cases which
@ -24,13 +26,13 @@ pub trait LineFormatter {
/// 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>;
T: Iterator<Item = RopeSlice<'a>>;
/// Converts a char index within a text into a visual 2d position.
/// The text to be formatted is passed as a grapheme iterator.
fn index_to_v2d<'a, T>(&'a self, g_iter: T, char_idx: usize) -> (usize, usize)
where
T: Iterator<Item = &'a str>;
T: Iterator<Item = RopeSlice<'a>>;
/// Converts a visual 2d position into a char index within a text.
/// The text to be formatted is passed as a grapheme iterator.
@ -41,7 +43,7 @@ pub trait LineFormatter {
rounding: (RoundingBehavior, RoundingBehavior),
) -> usize
where
T: Iterator<Item = &'a str>;
T: Iterator<Item = RopeSlice<'a>>;
fn index_to_horizontal_v2d(&self, buf: &Buffer, char_idx: usize) -> usize {
let (line_i, col_i) = buf.index_to_line_col(char_idx);
@ -53,7 +55,7 @@ pub trait LineFormatter {
// Get an iter into the right block
let a = line_block * LINE_BLOCK_LENGTH;
let b = min(line.len_chars(), (line_block + 1) * LINE_BLOCK_LENGTH);
let g_iter = line.slice(a..b).graphemes();
let g_iter = RopeGraphemes::new(&line.slice(a..b));
return self.index_to_v2d(g_iter, col_i_adjusted).1;
}
@ -77,10 +79,10 @@ pub trait LineFormatter {
let mut line = buf.get_line(line_i);
let (mut y, x) = self.index_to_v2d(
line.slice(
RopeGraphemes::new(&line.slice(
(line_block * LINE_BLOCK_LENGTH)
..min(line.len_chars(), (line_block + 1) * LINE_BLOCK_LENGTH),
).graphemes(),
)),
col_i_adjusted,
);
@ -90,10 +92,10 @@ pub trait LineFormatter {
let mut block_index: usize = line_block;
loop {
line = buf.get_line(line_i);
let (h, _) = self.dimensions(line.slice(
let (h, _) = self.dimensions(RopeGraphemes::new(&line.slice(
(block_index * LINE_BLOCK_LENGTH)
..min(line.len_chars(), (block_index + 1) * LINE_BLOCK_LENGTH),
).graphemes());
)));
if new_y >= 0 && new_y < h as isize {
y = new_y as usize;
@ -127,10 +129,10 @@ pub trait LineFormatter {
} else {
block_index -= 1;
}
let (h, _) = self.dimensions(line.slice(
let (h, _) = self.dimensions(RopeGraphemes::new(&line.slice(
(block_index * LINE_BLOCK_LENGTH)
..min(line.len_chars(), (block_index + 1) * LINE_BLOCK_LENGTH),
).graphemes());
)));
new_y += h as isize;
} else {
unreachable!();
@ -145,7 +147,7 @@ pub trait LineFormatter {
..min(line.len_chars(), (block_index + 1) * LINE_BLOCK_LENGTH),
);
let block_col_i = min(
self.v2d_to_index(block_slice.graphemes(), (y, x), rounding),
self.v2d_to_index(RopeGraphemes::new(&block_slice), (y, x), rounding),
LINE_BLOCK_LENGTH - 1,
);
col_i = (block_index * LINE_BLOCK_LENGTH) + block_col_i;
@ -173,11 +175,11 @@ pub trait LineFormatter {
// Calculate the horizontal position
let (v, _) = self.index_to_v2d(
line.slice(start_index..end_index).graphemes(),
RopeGraphemes::new(&line.slice(start_index..end_index)),
col_i_adjusted,
);
let block_col_i = self.v2d_to_index(
line.slice(start_index..end_index).graphemes(),
RopeGraphemes::new(&line.slice(start_index..end_index)),
(v, horizontal),
(RoundingBehavior::Floor, rounding),
);

View File

@ -2,6 +2,7 @@
//! Misc helpful utility functions for TextBuffer related stuff.
use std::iter::repeat;
use ropey::RopeSlice;
use unicode_segmentation::UnicodeSegmentation;
pub fn is_line_ending(text: &str) -> bool {
@ -13,6 +14,10 @@ pub fn is_line_ending(text: &str) -> bool {
}
}
pub fn rope_slice_is_line_ending(text: &RopeSlice) -> bool {
rope_slice_to_line_ending(text) != LineEnding::None
}
pub fn is_whitespace(text: &str) -> bool {
// TODO: this is a naive categorization of whitespace characters.
// For better categorization these should be split up into groups
@ -44,6 +49,34 @@ pub fn is_whitespace(text: &str) -> bool {
}
}
pub fn rope_slice_is_whitespace(text: &RopeSlice) -> bool {
// TODO: this is a naive categorization of whitespace characters.
// For better categorization these should be split up into groups
// based on e.g. breaking vs non-breaking spaces, among other things.
text == "\u{0020}" // SPACE
|| text == "\u{0009}" // CHARACTER TABULATION
|| text == "\u{00A0}" // NO-BREAK SPACE
//|| "\u{1680}" // OGHAM SPACE MARK (here for completeness, but usually displayed as a dash, not as whitespace)
|| text == "\u{180E}" // MONGOLIAN VOWEL SEPARATOR
|| text == "\u{2000}" // EN QUAD
|| text == "\u{2001}" // EM QUAD
|| text == "\u{2002}" // EN SPACE
|| text == "\u{2003}" // EM SPACE
|| text == "\u{2004}" // THREE-PER-EM SPACE
|| text == "\u{2005}" // FOUR-PER-EM SPACE
|| text == "\u{2006}" // SIX-PER-EM SPACE
|| text == "\u{2007}" // FIGURE SPACE
|| text == "\u{2008}" // PUNCTUATION SPACE
|| text == "\u{2009}" // THIN SPACE
|| text == "\u{200A}" // HAIR SPACE
|| text == "\u{200B}" // ZERO WIDTH SPACE
|| text == "\u{202F}" // NARROW NO-BREAK SPACE
|| text == "\u{205F}" // MEDIUM MATHEMATICAL SPACE
|| text == "\u{3000}" // IDEOGRAPHIC SPACE
|| text == "\u{FEFF}" // ZERO WIDTH NO-BREAK SPACE
}
pub fn line_ending_count(text: &str) -> usize {
let mut count = 0;
for g in UnicodeSegmentation::graphemes(text, true) {
@ -286,6 +319,29 @@ pub fn str_to_line_ending(g: &str) -> LineEnding {
}
}
pub fn rope_slice_to_line_ending(g: &RopeSlice) -> LineEnding {
if g == "\u{000D}\u{000A}" {
LineEnding::CRLF
} else if g == "\u{000A}" {
LineEnding::LF
} else if g == "\u{000B}" {
LineEnding::VT
} else if g == "\u{000C}" {
LineEnding::FF
} else if g == "\u{000D}" {
LineEnding::CR
} else if g == "\u{0085}" {
LineEnding::NEL
} else if g == "\u{2028}" {
LineEnding::LS
} else if g == "\u{2029}" {
LineEnding::PS
} else {
// Not a line ending
LineEnding::None
}
}
pub fn line_ending_to_str(ending: LineEnding) -> &'static str {
LINE_ENDINGS[ending as usize]
}

View File

@ -1,7 +1,8 @@
use std::cmp::max;
use unicode_width::UnicodeWidthStr;
use string_utils::{is_line_ending, is_whitespace};
use ropey::RopeSlice;
use utils::grapheme_width;
use string_utils::{rope_slice_is_line_ending, rope_slice_is_whitespace};
use formatter::{LineFormatter, RoundingBehavior};
pub enum WrapType {
@ -47,7 +48,7 @@ impl ConsoleLineFormatter {
pub fn iter<'a, T>(&'a self, g_iter: T) -> ConsoleLineFormatterVisIter<'a, T>
where
T: Iterator<Item = &'a str>,
T: Iterator<Item = RopeSlice<'a>>,
{
ConsoleLineFormatterVisIter::<'a, T> {
grapheme_iter: g_iter,
@ -68,7 +69,7 @@ impl LineFormatter for ConsoleLineFormatter {
fn dimensions<'a, T>(&'a self, g_iter: T) -> (usize, usize)
where
T: Iterator<Item = &'a str>,
T: Iterator<Item = RopeSlice<'a>>,
{
let mut dim: (usize, usize) = (0, 0);
@ -83,7 +84,7 @@ impl LineFormatter for ConsoleLineFormatter {
fn index_to_v2d<'a, T>(&'a self, g_iter: T, char_idx: usize) -> (usize, usize)
where
T: Iterator<Item = &'a str>,
T: Iterator<Item = RopeSlice<'a>>,
{
let mut pos = (0, 0);
let mut i = 0;
@ -109,7 +110,7 @@ impl LineFormatter for ConsoleLineFormatter {
_: (RoundingBehavior, RoundingBehavior),
) -> usize
where
T: Iterator<Item = &'a str>,
T: Iterator<Item = RopeSlice<'a>>,
{
// TODO: handle rounding modes
let mut prev_i = 0;
@ -137,7 +138,7 @@ impl LineFormatter for ConsoleLineFormatter {
// ===================================================================
pub struct ConsoleLineFormatterVisIter<'a, T>
where
T: Iterator<Item = &'a str>,
T: Iterator<Item = RopeSlice<'a>>,
{
grapheme_iter: T,
f: &'a ConsoleLineFormatter,
@ -146,15 +147,15 @@ where
indent: usize,
indent_found: bool,
word_buf: Vec<&'a str>,
word_buf: Vec<RopeSlice<'a>>,
word_i: usize,
}
impl<'a, T> ConsoleLineFormatterVisIter<'a, T>
where
T: Iterator<Item = &'a str>,
T: Iterator<Item = RopeSlice<'a>>,
{
fn next_nowrap(&mut self, g: &'a str) -> Option<(&'a str, (usize, usize), usize)> {
fn next_nowrap(&mut self, g: RopeSlice<'a>) -> Option<(RopeSlice<'a>, (usize, usize), usize)> {
let width = grapheme_vis_width_at_vis_pos(g, self.pos.1, self.f.tab_width as usize);
let pos = self.pos;
@ -164,9 +165,9 @@ where
fn next_charwrap(
&mut self,
g: &'a str,
g: RopeSlice<'a>,
wrap_width: usize,
) -> Option<(&'a str, (usize, usize), usize)> {
) -> Option<(RopeSlice<'a>, (usize, usize), usize)> {
let width = grapheme_vis_width_at_vis_pos(g, self.pos.1, self.f.tab_width as usize);
if (self.pos.1 + width) > wrap_width {
@ -198,7 +199,7 @@ where
}
} else {
if !self.indent_found {
if is_whitespace(g) {
if rope_slice_is_whitespace(&g) {
self.indent += width;
} else {
self.indent_found = true;
@ -214,11 +215,11 @@ where
impl<'a, T> Iterator for ConsoleLineFormatterVisIter<'a, T>
where
T: Iterator<Item = &'a str>,
T: Iterator<Item = RopeSlice<'a>>,
{
type Item = (&'a str, (usize, usize), usize);
type Item = (RopeSlice<'a>, (usize, usize), usize);
fn next(&mut self) -> Option<(&'a str, (usize, usize), usize)> {
fn next(&mut self) -> Option<(RopeSlice<'a>, (usize, usize), usize)> {
match self.f.wrap_type {
WrapType::NoWrap => {
if let Some(g) = self.grapheme_iter.next() {
@ -249,14 +250,14 @@ where
self.f.tab_width as usize,
);
word_width += width;
if is_whitespace(g) {
if rope_slice_is_whitespace(&g) {
break;
}
}
if self.word_buf.len() == 0 {
return None;
} else if !self.indent_found && !is_whitespace(self.word_buf[0]) {
} else if !self.indent_found && !rope_slice_is_whitespace(&self.word_buf[0]) {
self.indent_found = true;
}
@ -300,27 +301,22 @@ where
/// Returns the visual width of a grapheme given a starting
/// position on a line.
fn grapheme_vis_width_at_vis_pos(g: &str, pos: usize, tab_width: usize) -> usize {
match g {
"\t" => {
fn grapheme_vis_width_at_vis_pos(g: RopeSlice, pos: usize, tab_width: usize) -> usize {
if g == "\t" {
let ending_pos = ((pos / tab_width) + 1) * tab_width;
return ending_pos - pos;
}
_ => {
if is_line_ending(g) {
} else if rope_slice_is_line_ending(&g) {
return 1;
} else {
return UnicodeWidthStr::width(g);
}
}
return grapheme_width(&g);
}
}
#[cfg(test)]
mod tests {
#![allow(unused_imports)]
use unicode_segmentation::UnicodeSegmentation;
use ropey::Rope;
use utils::RopeGraphemes;
use super::*;
use formatter::{LineFormatter, LINE_BLOCK_LENGTH};
use formatter::RoundingBehavior::{Ceiling, Floor, Round};
@ -328,7 +324,7 @@ mod tests {
#[test]
fn dimensions_1() {
let text = "Hello there, stranger!"; // 22 graphemes long
let text = Rope::from_str("Hello there, stranger!"); // 22 graphemes long
let mut f = ConsoleLineFormatter::new(4);
f.wrap_type = WrapType::CharWrap(0);
@ -337,14 +333,14 @@ mod tests {
f.set_wrap_width(80);
assert_eq!(
f.dimensions(UnicodeSegmentation::graphemes(text, true)),
f.dimensions(RopeGraphemes::new(&text.slice(..))),
(1, 22)
);
}
#[test]
fn dimensions_2() {
let text = "Hello there, stranger! How are you doing this fine day?"; // 56 graphemes long
let text = Rope::from_str("Hello there, stranger! How are you doing this fine day?"); // 56 graphemes long
let mut f = ConsoleLineFormatter::new(4);
f.wrap_type = WrapType::CharWrap(0);
@ -353,7 +349,7 @@ mod tests {
f.set_wrap_width(12);
assert_eq!(
f.dimensions(UnicodeSegmentation::graphemes(text, true)),
f.dimensions(RopeGraphemes::new(&text.slice(..))),
(5, 12)
);
}
@ -361,7 +357,7 @@ mod tests {
#[test]
fn dimensions_3() {
// 55 graphemes long
let text = "税マイミ文末\
let text = Rope::from_str("税マイミ文末\
\
\
\
@ -370,7 +366,7 @@ mod tests {
\
\
\
";
");
let mut f = ConsoleLineFormatter::new(4);
f.wrap_type = WrapType::CharWrap(0);
@ -379,7 +375,7 @@ mod tests {
f.set_wrap_width(12);
assert_eq!(
f.dimensions(UnicodeSegmentation::graphemes(text, true)),
f.dimensions(RopeGraphemes::new(&text.slice(..))),
(10, 12)
);
}
@ -387,7 +383,7 @@ mod tests {
#[test]
fn dimensions_4() {
// 55 graphemes long
let text = "税マイミ文末\
let text = Rope::from_str("税マイミ文末\
\
\
\
@ -396,7 +392,7 @@ mod tests {
\
\
\
";
");
let mut f = ConsoleLineFormatter::new(4);
f.wrap_type = WrapType::WordWrap(0);
@ -405,14 +401,14 @@ mod tests {
f.set_wrap_width(12);
assert_eq!(
f.dimensions(UnicodeSegmentation::graphemes(text, true)),
f.dimensions(RopeGraphemes::new(&text.slice(..))),
(10, 12)
);
}
#[test]
fn index_to_v2d_1() {
let text = "Hello there, stranger!"; // 22 graphemes long
let text = Rope::from_str("Hello there, stranger!"); // 22 graphemes long
let mut f = ConsoleLineFormatter::new(4);
f.wrap_type = WrapType::CharWrap(0);
@ -421,26 +417,26 @@ mod tests {
f.set_wrap_width(80);
assert_eq!(
f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 0),
f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 0),
(0, 0)
);
assert_eq!(
f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 5),
f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 5),
(0, 5)
);
assert_eq!(
f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 22),
f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 22),
(0, 22)
);
assert_eq!(
f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 23),
f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 23),
(0, 22)
);
}
#[test]
fn index_to_v2d_2() {
let text = "Hello there, stranger! How are you doing this fine day?"; // 56 graphemes long
let text = Rope::from_str("Hello there, stranger! How are you doing this fine day?"); // 56 graphemes long
let mut f = ConsoleLineFormatter::new(4);
f.wrap_type = WrapType::CharWrap(0);
@ -449,79 +445,79 @@ mod tests {
f.set_wrap_width(12);
assert_eq!(
f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 0),
f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 0),
(0, 0)
);
assert_eq!(
f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 5),
f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 5),
(0, 5)
);
assert_eq!(
f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 11),
f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 11),
(0, 11)
);
assert_eq!(
f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 12),
f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 12),
(1, 0)
);
assert_eq!(
f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 15),
f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 15),
(1, 3)
);
assert_eq!(
f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 23),
f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 23),
(1, 11)
);
assert_eq!(
f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 24),
f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 24),
(2, 0)
);
assert_eq!(
f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 28),
f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 28),
(2, 4)
);
assert_eq!(
f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 35),
f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 35),
(2, 11)
);
assert_eq!(
f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 36),
f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 36),
(3, 0)
);
assert_eq!(
f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 43),
f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 43),
(3, 7)
);
assert_eq!(
f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 47),
f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 47),
(3, 11)
);
assert_eq!(
f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 48),
f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 48),
(4, 0)
);
assert_eq!(
f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 50),
f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 50),
(4, 2)
);
assert_eq!(
f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 56),
f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 56),
(4, 8)
);
assert_eq!(
f.index_to_v2d(UnicodeSegmentation::graphemes(text, true), 57),
f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 57),
(4, 8)
);
}
#[test]
fn v2d_to_index_1() {
let text = "Hello there, stranger!"; // 22 graphemes long
let text = Rope::from_str("Hello there, stranger!"); // 22 graphemes long
let mut f = ConsoleLineFormatter::new(4);
f.wrap_type = WrapType::CharWrap(0);
@ -531,7 +527,7 @@ mod tests {
assert_eq!(
f.v2d_to_index(
UnicodeSegmentation::graphemes(text, true),
RopeGraphemes::new(&text.slice(..)),
(0, 0),
(Floor, Floor)
),
@ -539,7 +535,7 @@ mod tests {
);
assert_eq!(
f.v2d_to_index(
UnicodeSegmentation::graphemes(text, true),
RopeGraphemes::new(&text.slice(..)),
(0, 5),
(Floor, Floor)
),
@ -547,7 +543,7 @@ mod tests {
);
assert_eq!(
f.v2d_to_index(
UnicodeSegmentation::graphemes(text, true),
RopeGraphemes::new(&text.slice(..)),
(0, 22),
(Floor, Floor)
),
@ -555,7 +551,7 @@ mod tests {
);
assert_eq!(
f.v2d_to_index(
UnicodeSegmentation::graphemes(text, true),
RopeGraphemes::new(&text.slice(..)),
(0, 23),
(Floor, Floor)
),
@ -563,7 +559,7 @@ mod tests {
);
assert_eq!(
f.v2d_to_index(
UnicodeSegmentation::graphemes(text, true),
RopeGraphemes::new(&text.slice(..)),
(1, 0),
(Floor, Floor)
),
@ -571,7 +567,7 @@ mod tests {
);
assert_eq!(
f.v2d_to_index(
UnicodeSegmentation::graphemes(text, true),
RopeGraphemes::new(&text.slice(..)),
(1, 1),
(Floor, Floor)
),
@ -581,7 +577,7 @@ mod tests {
#[test]
fn v2d_to_index_2() {
let text = "Hello there, stranger! How are you doing this fine day?"; // 56 graphemes long
let text = Rope::from_str("Hello there, stranger! How are you doing this fine day?"); // 56 graphemes long
let mut f = ConsoleLineFormatter::new(4);
f.wrap_type = WrapType::CharWrap(0);
@ -591,7 +587,7 @@ mod tests {
assert_eq!(
f.v2d_to_index(
UnicodeSegmentation::graphemes(text, true),
RopeGraphemes::new(&text.slice(..)),
(0, 0),
(Floor, Floor)
),
@ -599,7 +595,7 @@ mod tests {
);
assert_eq!(
f.v2d_to_index(
UnicodeSegmentation::graphemes(text, true),
RopeGraphemes::new(&text.slice(..)),
(0, 11),
(Floor, Floor)
),
@ -607,7 +603,7 @@ mod tests {
);
assert_eq!(
f.v2d_to_index(
UnicodeSegmentation::graphemes(text, true),
RopeGraphemes::new(&text.slice(..)),
(0, 12),
(Floor, Floor)
),
@ -616,7 +612,7 @@ mod tests {
assert_eq!(
f.v2d_to_index(
UnicodeSegmentation::graphemes(text, true),
RopeGraphemes::new(&text.slice(..)),
(1, 0),
(Floor, Floor)
),
@ -624,7 +620,7 @@ mod tests {
);
assert_eq!(
f.v2d_to_index(
UnicodeSegmentation::graphemes(text, true),
RopeGraphemes::new(&text.slice(..)),
(1, 11),
(Floor, Floor)
),
@ -632,7 +628,7 @@ mod tests {
);
assert_eq!(
f.v2d_to_index(
UnicodeSegmentation::graphemes(text, true),
RopeGraphemes::new(&text.slice(..)),
(1, 12),
(Floor, Floor)
),
@ -641,7 +637,7 @@ mod tests {
assert_eq!(
f.v2d_to_index(
UnicodeSegmentation::graphemes(text, true),
RopeGraphemes::new(&text.slice(..)),
(2, 0),
(Floor, Floor)
),
@ -649,7 +645,7 @@ mod tests {
);
assert_eq!(
f.v2d_to_index(
UnicodeSegmentation::graphemes(text, true),
RopeGraphemes::new(&text.slice(..)),
(2, 11),
(Floor, Floor)
),
@ -657,7 +653,7 @@ mod tests {
);
assert_eq!(
f.v2d_to_index(
UnicodeSegmentation::graphemes(text, true),
RopeGraphemes::new(&text.slice(..)),
(2, 12),
(Floor, Floor)
),
@ -666,7 +662,7 @@ mod tests {
assert_eq!(
f.v2d_to_index(
UnicodeSegmentation::graphemes(text, true),
RopeGraphemes::new(&text.slice(..)),
(3, 0),
(Floor, Floor)
),
@ -674,7 +670,7 @@ mod tests {
);
assert_eq!(
f.v2d_to_index(
UnicodeSegmentation::graphemes(text, true),
RopeGraphemes::new(&text.slice(..)),
(3, 11),
(Floor, Floor)
),
@ -682,7 +678,7 @@ mod tests {
);
assert_eq!(
f.v2d_to_index(
UnicodeSegmentation::graphemes(text, true),
RopeGraphemes::new(&text.slice(..)),
(3, 12),
(Floor, Floor)
),
@ -691,7 +687,7 @@ mod tests {
assert_eq!(
f.v2d_to_index(
UnicodeSegmentation::graphemes(text, true),
RopeGraphemes::new(&text.slice(..)),
(4, 0),
(Floor, Floor)
),
@ -699,7 +695,7 @@ mod tests {
);
assert_eq!(
f.v2d_to_index(
UnicodeSegmentation::graphemes(text, true),
RopeGraphemes::new(&text.slice(..)),
(4, 7),
(Floor, Floor)
),
@ -707,7 +703,7 @@ mod tests {
);
assert_eq!(
f.v2d_to_index(
UnicodeSegmentation::graphemes(text, true),
RopeGraphemes::new(&text.slice(..)),
(4, 8),
(Floor, Floor)
),
@ -715,7 +711,7 @@ mod tests {
);
assert_eq!(
f.v2d_to_index(
UnicodeSegmentation::graphemes(text, true),
RopeGraphemes::new(&text.slice(..)),
(4, 9),
(Floor, Floor)
),

View File

@ -10,8 +10,8 @@ use termion::input::TermRead;
use editor::Editor;
use formatter::{block_index_and_offset, LineFormatter, LINE_BLOCK_LENGTH};
use self::formatter::ConsoleLineFormatter;
use string_utils::{is_line_ending, line_ending_to_str, LineEnding};
use utils::digit_count;
use string_utils::{rope_slice_is_line_ending, line_ending_to_str, LineEnding};
use utils::{digit_count, RopeGraphemes};
pub mod formatter;
mod screen;
@ -367,15 +367,14 @@ impl TermUI {
.line_col_to_index((line_index, line_block_index * LINE_BLOCK_LENGTH));
let temp_line = editor.buffer.get_line(line_index);
let (vis_line_offset, _) = editor.formatter.index_to_v2d(
temp_line
RopeGraphemes::new(&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 - char_index,
);
@ -412,9 +411,9 @@ impl TermUI {
let mut last_pos_y = 0;
let mut lines_traversed: usize = 0;
let line_len = line.len_chars();
let mut g_iter = editor.formatter.iter(line.slice(
let mut g_iter = editor.formatter.iter(RopeGraphemes::new(&line.slice(
(line_block_index * LINE_BLOCK_LENGTH)..line_len,
).graphemes());
)));
loop {
if let Some((g, (pos_y, pos_x), width)) = g_iter.next() {
@ -444,7 +443,7 @@ impl TermUI {
}
// Actually print the character
if is_line_ending(g) {
if rope_slice_is_line_ending(&g) {
if at_cursor {
self.screen.draw(
px as usize,
@ -476,17 +475,17 @@ impl TermUI {
}
} else {
if at_cursor {
self.screen.draw(
self.screen.draw_rope_slice(
px as usize,
py as usize,
g,
&g,
Style(Color::Black, Color::White),
);
} else {
self.screen.draw(
self.screen.draw_rope_slice(
px as usize,
py as usize,
g,
&g,
Style(Color::White, Color::Black),
);
}
@ -503,9 +502,9 @@ impl TermUI {
line_block_index += 1;
line_g_index = 0;
let line_len = line.len_chars();
g_iter = editor.formatter.iter(line.slice(
g_iter = editor.formatter.iter(RopeGraphemes::new(&line.slice(
(line_block_index * LINE_BLOCK_LENGTH)..line_len,
).graphemes());
)));
lines_traversed += 1;
}
}

View File

@ -3,6 +3,8 @@ use std::cell::RefCell;
use std::io;
use std::io::{BufWriter, Write};
use ropey::RopeSlice;
use utils::{RopeGraphemes, grapheme_width};
use super::smallstring::SmallString;
use unicode_width::UnicodeWidthStr;
use unicode_segmentation::UnicodeSegmentation;
@ -110,6 +112,28 @@ impl Screen {
}
}
pub(crate) fn draw_rope_slice(&self, x: usize, y: usize, text: &RopeSlice, style: Style) {
if y < self.h {
let mut buf = self.buf.borrow_mut();
let mut x = x;
for g in RopeGraphemes::new(&text) {
let width = grapheme_width(&g);
if width > 0 {
if x < self.w {
buf[y * self.w + x] = Some((style, SmallString::from_rope_slice(&g)));
}
x += 1;
for _ in 0..(width - 1) {
if x < self.w {
buf[y * self.w + x] = None;
}
x += 1;
}
}
}
}
}
pub(crate) fn hide_cursor(&self) {
write!(self.out.borrow_mut(), "{}", termion::cursor::Hide).unwrap();
}

View File

@ -5,6 +5,8 @@ use std::ops::Deref;
use std::ptr;
use std::str;
use ropey::RopeSlice;
use smallvec::SmallVec;
#[derive(Clone, Default)]
@ -37,6 +39,17 @@ impl SmallString {
string
}
/// Creates a new `SmallString` with the same contents as the given `&str`.
pub fn from_rope_slice(text: &RopeSlice) -> Self {
let mut string = SmallString::with_capacity(text.len_bytes());
let mut idx = 0;
for chunk in text.chunks() {
unsafe { string.insert_bytes(idx, chunk.as_bytes()) };
idx += chunk.len();
}
string
}
/// Appends a `&str` to end the of the `SmallString`.
pub fn push_str(&mut self, string: &str) {
let len = self.len();

View File

@ -1,3 +1,7 @@
use ropey::{RopeSlice, iter::Chunks};
use unicode_segmentation::{GraphemeCursor, GraphemeIncomplete};
use unicode_width::UnicodeWidthStr;
pub fn digit_count(mut n: u32, b: u32) -> u32 {
let mut d = 0;
loop {
@ -9,6 +13,169 @@ pub fn digit_count(mut n: u32, b: u32) -> u32 {
}
}
//=============================================================
pub fn grapheme_width(slice: &RopeSlice) -> usize {
// TODO: use a small stack-allocated buffer to handle the common case
// without allocation.
let s = slice.to_string();
return UnicodeWidthStr::width(&s[..]);
}
/// Finds the previous grapheme boundary before the given char position.
pub fn prev_grapheme_boundary(slice: &RopeSlice, char_idx: usize) -> usize {
// Bounds check
debug_assert!(char_idx <= slice.len_chars());
// We work with bytes for this, so convert.
let byte_idx = slice.char_to_byte(char_idx);
// Get the chunk with our byte index in it, and calculate its starting
// byte within the total rope slice.
let (mut chunk, byte_offset) = slice.chunk_at_byte(byte_idx);
let mut chunk_start_idx = byte_idx - byte_offset;
// Set up the grapheme cursor.
let mut gc = GraphemeCursor::new(byte_idx, slice.len_bytes(), true);
// Find the previous grapheme cluster boundary.
loop {
match gc.prev_boundary(chunk, chunk_start_idx) {
Ok(None) => return 0,
Ok(Some(n)) => return slice.byte_to_char(n),
Err(GraphemeIncomplete::PrevChunk) => {
chunk = slice.chunk_at_byte(chunk_start_idx - 1).0;
chunk_start_idx -= chunk.len();
}
Err(GraphemeIncomplete::PreContext(n)) => {
let (ctx_chunk, _) = slice.chunk_at_byte(n - 1);
gc.provide_context(ctx_chunk, n - ctx_chunk.len());
}
_ => unreachable!(),
}
}
}
/// Finds the next grapheme boundary after the given char position.
pub fn next_grapheme_boundary(slice: &RopeSlice, char_idx: usize) -> usize {
// Bounds check
debug_assert!(char_idx <= slice.len_chars());
// We work with bytes for this, so convert.
let byte_idx = slice.char_to_byte(char_idx);
// Get the chunk with our byte index in it, and calculate its starting
// byte within the total rope slice.
let (mut chunk, byte_offset) = slice.chunk_at_byte(byte_idx);
let mut chunk_start_idx = byte_idx - byte_offset;
// Set up the grapheme cursor.
let mut gc = GraphemeCursor::new(byte_idx, slice.len_bytes(), true);
// Find the next grapheme cluster boundary.
loop {
match gc.next_boundary(chunk, chunk_start_idx) {
Ok(None) => return slice.len_chars(),
Ok(Some(n)) => return slice.byte_to_char(n),
Err(GraphemeIncomplete::NextChunk) => {
chunk_start_idx += chunk.len();
chunk = slice.chunk_at_byte(chunk_start_idx).0;
}
Err(GraphemeIncomplete::PreContext(n)) => {
let (ctx_chunk, _) = slice.chunk_at_byte(n - 1);
gc.provide_context(ctx_chunk, n - ctx_chunk.len());
}
_ => unreachable!(),
}
}
}
/// Returns whether the given char position is a grapheme boundary.
pub fn is_grapheme_boundary(slice: &RopeSlice, char_idx: usize) -> bool {
// Bounds check
debug_assert!(char_idx <= slice.len_chars());
// We work with bytes for this, so convert.
let byte_idx = slice.char_to_byte(char_idx);
// Get the chunk with our byte index in it, and calculate its starting
// byte within the total rope slice.
let (chunk, byte_offset) = slice.chunk_at_byte(byte_idx);
let chunk_start_idx = byte_idx - byte_offset;
// Set up the grapheme cursor.
let mut gc = GraphemeCursor::new(byte_idx, slice.len_bytes(), true);
// Determine if the given position is a grapheme cluster boundary.
loop {
match gc.is_boundary(chunk, chunk_start_idx) {
Ok(n) => return n,
Err(GraphemeIncomplete::PreContext(n)) => {
let (ctx_chunk, _) = slice.chunk_at_byte(n - 1);
gc.provide_context(ctx_chunk, n - ctx_chunk.len());
}
_ => unreachable!(),
}
}
}
/// An iterator over the graphemes of a RopeSlice.
pub struct RopeGraphemes<'a> {
text: RopeSlice<'a>,
chunks: Chunks<'a>,
cur_chunk: &'a str,
cur_chunk_start: usize,
cursor: GraphemeCursor,
}
impl<'a> RopeGraphemes<'a> {
pub fn new<'b>(slice: &RopeSlice<'b>) -> RopeGraphemes<'b> {
let mut chunks = slice.chunks();
let first_chunk = chunks.next().unwrap_or("");
RopeGraphemes {
text: *slice,
chunks: chunks,
cur_chunk: first_chunk,
cur_chunk_start: 0,
cursor: GraphemeCursor::new(0, slice.len_bytes(), true),
}
}
}
impl<'a> Iterator for RopeGraphemes<'a> {
type Item = RopeSlice<'a>;
fn next(&mut self) -> Option<RopeSlice<'a>> {
let a = self.cursor.cur_cursor();
let b;
loop {
match self.cursor
.next_boundary(self.cur_chunk, self.cur_chunk_start)
{
Ok(None) => {
return None;
}
Ok(Some(n)) => {
b = n;
break;
}
Err(GraphemeIncomplete::NextChunk) => {
self.cur_chunk_start += self.cur_chunk.len();
self.cur_chunk = self.chunks.next().unwrap_or("");
}
_ => unreachable!(),
}
}
let a_char = self.text.byte_to_char(a);
let b_char = self.text.byte_to_char(b);
Some(self.text.slice(a_char..b_char))
}
}
//=============================================================
#[cfg(test)]
mod tests {
use super::*;