Port to Ropey 2.0-alpha.

This commit is contained in:
Nathan Vegdahl 2024-06-08 07:26:03 +02:00
parent b1391ebb35
commit cd2314905a
10 changed files with 168 additions and 228 deletions

6
Cargo.lock generated
View File

@ -686,11 +686,9 @@ dependencies = [
[[package]]
name = "ropey"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93411e420bcd1a75ddd1dc3caf18c23155eda2c090631a85af21ba19e97093b5"
version = "2.0.0-alpha"
source = "git+https://github.com/cessen/ropey?branch=2.0-alpha#013c3cf6d3c2647b79af3f346fff7610efe37c5a"
dependencies = [
"smallvec",
"str_indices",
]

View File

@ -18,8 +18,8 @@ path = "src/main.rs"
debug = true
[dependencies]
ropey = "1"
# ropey = { git = "https://github.com/cessen/ropey", branch = "master" }
# ropey = "1"
ropey = { git = "https://github.com/cessen/ropey", branch = "2.0-alpha" }
unicode-segmentation = "1.7"
unicode-width = "0.1"
clap = "2"

View File

@ -6,13 +6,14 @@ use std::{
io,
};
use backend::{buffer::Buffer, marks::Mark};
use backend::{
buffer::{Buffer, BUFLINE},
marks::Mark,
};
use crate::{
formatter::LineFormatter,
graphemes::{
is_grapheme_boundary, nth_next_grapheme_boundary, nth_prev_grapheme_boundary, RopeGraphemes,
},
graphemes::{is_grapheme_boundary, nth_next_grapheme_boundary, nth_prev_grapheme_boundary},
string_utils::{rope_slice_to_line_ending, LineEnding},
utils::digit_count,
};
@ -77,18 +78,10 @@ impl Editor {
let mut line_ending_histogram: [usize; 8] = [0, 0, 0, 0, 0, 0, 0, 0];
// Collect statistics on the first 100 lines
for line in self.buffer.text.lines().take(100) {
for line in self.buffer.text.lines(BUFLINE).take(100) {
// Get the line ending
let ending = if line.len_chars() == 1 {
let g = RopeGraphemes::new(&line.slice((line.len_chars() - 1)..))
.last()
.unwrap();
rope_slice_to_line_ending(&g)
} else if line.len_chars() > 1 {
let g = RopeGraphemes::new(&line.slice((line.len_chars() - 2)..))
.last()
.unwrap();
rope_slice_to_line_ending(&g)
let ending = if let Some(idx) = line.trailing_line_break_idx(BUFLINE) {
rope_slice_to_line_ending(line.slice(idx..))
} else {
LineEnding::None
};
@ -157,7 +150,7 @@ impl Editor {
let mut last_indent = (false, 0usize); // (was_tabs, indent_count)
// Collect statistics on the first 1000 lines
for line in self.buffer.text.lines().take(1000) {
for line in self.buffer.text.lines(BUFLINE).take(1000) {
let mut c_iter = line.chars();
match c_iter.next() {
Some('\t') => {
@ -230,7 +223,8 @@ impl Editor {
/// Updates the view dimensions.
pub fn update_dim(&mut self, h: usize, w: usize) {
let line_count_digits = digit_count(self.buffer.text.len_lines() as u32, 10) as usize;
let line_count_digits =
digit_count(self.buffer.text.len_lines(BUFLINE) as u32, 10) as usize;
self.editor_dim = (h, w);
// Minus 1 vertically for the header, minus two more than the digits in
@ -289,7 +283,7 @@ impl Editor {
self.buffer.edit((range.start, range.end), text);
// Adjust cursor position.
let len = text.chars().count();
let len = text.len();
self.buffer.mark_sets[self.c_msi][0].head = range.start + len;
self.buffer.mark_sets[self.c_msi][0].tail = range.start + len;
self.buffer.mark_sets[self.c_msi][0].hh_pos = None;
@ -362,7 +356,7 @@ impl Editor {
let range = mark.range();
// Do nothing if there's nothing to delete.
if range.end == self.buffer.text.len_chars() {
if range.end == self.buffer.text.len_bytes() {
return;
}
@ -398,7 +392,7 @@ impl Editor {
}
pub fn cursor_to_end_of_buffer(&mut self) {
let end = self.buffer.text.len_chars();
let end = self.buffer.text.len_bytes();
self.buffer.mark_sets[self.c_msi].clear();
self.buffer.mark_sets[self.c_msi].add_mark(Mark::new(end, end));
@ -487,8 +481,8 @@ impl Editor {
if temp_index == mark.head {
// We were already at the bottom.
mark.head = self.buffer.text.len_chars();
mark.tail = self.buffer.text.len_chars();
mark.head = self.buffer.text.len_bytes();
mark.tail = self.buffer.text.len_bytes();
mark.hh_pos = None;
} else {
mark.head = temp_index;
@ -541,7 +535,7 @@ impl Editor {
let pos = self
.buffer
.text
.line_to_char(n.min(self.buffer.text.len_lines()));
.line_to_byte(n.min(self.buffer.text.len_lines(BUFLINE)), BUFLINE);
let pos = self.formatter.set_horizontal(
&self.buffer.text,
pos,

View File

@ -2,9 +2,10 @@ use std::borrow::Cow;
use ropey::{Rope, RopeSlice};
use backend::buffer::BUFLINE;
use crate::{
graphemes::{grapheme_width, is_grapheme_boundary, prev_grapheme_boundary, RopeGraphemes},
string_utils::char_count,
string_utils::str_is_whitespace,
};
@ -39,16 +40,16 @@ impl LineFormatter {
}
/// Returns an iterator over the blocks of the buffer, starting at the
/// block containing the given char. Also returns the offset of that char
/// block containing the given byte. Also returns the offset of that byte
/// relative to the start of the first block.
pub fn iter<'b>(&'b self, buf: &'b Rope, char_idx: usize) -> (Blocks<'b>, usize) {
pub fn iter<'b>(&'b self, buf: &'b Rope, byte_idx: usize) -> (Blocks<'b>, usize) {
// Get the line.
let (line_i, col_i) = {
let line_idx = buf.char_to_line(char_idx);
let col_idx = char_idx - buf.line_to_char(line_idx);
let line_idx = buf.byte_to_line(byte_idx, BUFLINE);
let col_idx = byte_idx - buf.line_to_byte(line_idx, BUFLINE);
(line_idx, col_idx)
};
let line = buf.line(line_i);
let line = buf.line(line_i, BUFLINE);
// Find the right block in the line, and the index within that block
let (block_index, block_range) = block_index_and_range(&line, col_i);
@ -66,9 +67,9 @@ impl LineFormatter {
)
}
/// Converts from char index to its formatted horizontal 2d position.
pub fn get_horizontal(&self, buf: &Rope, char_idx: usize) -> usize {
let (_, vis_iter, char_offset) = self.block_vis_iter_and_char_offset(buf, char_idx);
/// Converts from byte index to its formatted horizontal 2d position.
pub fn get_horizontal(&self, buf: &Rope, byte_idx: usize) -> usize {
let (_, vis_iter, byte_offset) = self.block_vis_iter_and_byte_offset(buf, byte_idx);
// Traverse the iterator and find the horizontal position of the char
// index.
@ -79,9 +80,9 @@ impl LineFormatter {
for (g, pos, width) in vis_iter {
hpos = pos.1;
last_width = width;
i += char_count(&g);
i += g.len();
if i > char_offset {
if i > byte_offset {
return hpos;
}
}
@ -91,14 +92,14 @@ impl LineFormatter {
return hpos + last_width;
}
/// Takes a char index and a desired visual horizontal position, and
/// returns a char index on the same visual line as the given index,
/// Takes a byte index and a desired visual horizontal position, and
/// returns a byte index on the same visual line as the given index,
/// but offset to have the desired horizontal position (or as close as is
/// possible.
pub fn set_horizontal(&self, buf: &Rope, char_idx: usize, horizontal: usize) -> usize {
let (_, vis_iter, char_offset) = self.block_vis_iter_and_char_offset(buf, char_idx);
pub fn set_horizontal(&self, buf: &Rope, byte_idx: usize, horizontal: usize) -> usize {
let (_, vis_iter, byte_offset) = self.block_vis_iter_and_byte_offset(buf, byte_idx);
let mut hpos_char_idx = None;
let mut hpos_byte_idx = None;
let mut i = 0;
let mut last_i = 0;
let mut last_pos = (0, 0);
@ -109,74 +110,74 @@ impl LineFormatter {
// that means the target was on the previous line but the line
// wasn't long enough, so return the index of the last grapheme
// of the previous line.
if i > char_offset {
return char_idx - char_offset + last_i;
if i > byte_offset {
return byte_idx - byte_offset + last_i;
}
// Otherwise reset and keep going.
hpos_char_idx = None;
hpos_byte_idx = None;
}
// Check if we found the horizontal position on this line,
// and set it if so.
if hpos_char_idx == None && (pos.1 + width) > horizontal {
hpos_char_idx = Some(i);
if hpos_byte_idx == None && (pos.1 + width) > horizontal {
hpos_byte_idx = Some(i);
}
// Check if we've found the horizontal position _and_ the passed
// char_idx on the same line, and return if so.
if (i + char_count(&g)) > char_offset && hpos_char_idx != None {
return char_idx - char_offset + hpos_char_idx.unwrap();
// byte_idx on the same line, and return if so.
if (i + g.len()) > byte_offset && hpos_byte_idx != None {
return byte_idx - byte_offset + hpos_byte_idx.unwrap();
}
last_pos = pos;
last_i = i;
i += char_count(&g);
i += g.len();
}
// If we reached the end of the text, return the last char index.
let end_i = char_idx - char_offset + i;
let end_last_i = char_idx - char_offset + last_i;
if buf.len_chars() == end_i {
let end_i = byte_idx - byte_offset + i;
let end_last_i = byte_idx - byte_offset + last_i;
if buf.len_bytes() == end_i {
return end_i;
} else {
return end_last_i;
}
}
/// Takes a char index and a visual vertical offset, and returns the char
/// Takes a byte index and a visual vertical offset, and returns the byte
/// index after that visual offset is applied.
pub fn offset_vertical(&self, buf: &Rope, char_idx: usize, v_offset: isize) -> usize {
let mut char_idx = char_idx;
pub fn offset_vertical(&self, buf: &Rope, byte_idx: usize, v_offset: isize) -> usize {
let mut byte_idx = byte_idx;
let mut v_offset = v_offset;
while v_offset != 0 {
// Get our block and the offset of the char inside it.
let (block, block_vis_iter, char_offset) =
self.block_vis_iter_and_char_offset(buf, char_idx);
// Get our block and the offset of the byte inside it.
let (block, block_vis_iter, byte_offset) =
self.block_vis_iter_and_byte_offset(buf, byte_idx);
// Get the vertical size of the block and the vertical
// position of the char_idx within it.
// position of the byte_idx within it.
let block_v_dim = block_vis_iter.clone().last().map(|n| (n.1).0).unwrap_or(0) + 1;
let char_v_pos = block_vis_iter.clone().vpos(char_offset);
let byte_v_pos = block_vis_iter.clone().vpos(byte_offset);
// Get the char's vertical position within the block after offset
// Get the byte's vertical position within the block after offset
// by v_offset.
let offset_char_v_pos = char_v_pos as isize + v_offset;
let offset_byte_v_pos = byte_v_pos as isize + v_offset;
// Check if the offset position is within the block or not,
// and handle appropriately.
if offset_char_v_pos < 0 {
if offset_byte_v_pos < 0 {
// If we're off the start of the block.
char_idx = char_idx.saturating_sub(char_offset + 1);
v_offset += char_v_pos as isize + 1;
if char_idx == 0 {
byte_idx = byte_idx.saturating_sub(byte_offset + 1);
v_offset += byte_v_pos as isize + 1;
if byte_idx == 0 {
break;
}
} else if offset_char_v_pos >= block_v_dim as isize {
} else if offset_byte_v_pos >= block_v_dim as isize {
// If we're off the end of the block.
char_idx = (char_idx + block.len_chars() - char_offset).min(buf.len_chars());
v_offset -= block_v_dim as isize - char_v_pos as isize;
if char_idx == buf.len_chars() {
byte_idx = (byte_idx + block.len_bytes() - byte_offset).min(buf.len_bytes());
v_offset -= block_v_dim as isize - byte_v_pos as isize;
if byte_idx == buf.len_bytes() {
break;
}
} else {
@ -184,18 +185,18 @@ impl LineFormatter {
// appropriate char index and return.
let mut i = 0;
for (g, pos, _) in block_vis_iter {
if pos.0 == offset_char_v_pos as usize {
if pos.0 == offset_byte_v_pos as usize {
break;
}
i += char_count(&g);
i += g.len();
}
char_idx -= char_offset;
char_idx += i;
byte_idx -= byte_offset;
byte_idx += i;
v_offset = 0;
}
}
return char_idx;
return byte_idx;
}
//----------------------------------------------------
@ -229,20 +230,20 @@ impl LineFormatter {
indent
}
/// Returns the appropriate BlockVisIter containing the given char, and the
/// char's offset within that iter.
fn block_vis_iter_and_char_offset<'b>(
/// Returns the appropriate BlockVisIter containing the given byte, and the
/// byte's offset within that iter.
fn block_vis_iter_and_byte_offset<'b>(
&self,
buf: &'b Rope,
char_idx: usize,
byte_idx: usize,
) -> (RopeSlice<'b>, BlockVisIter<'b>, usize) {
let line_i = buf.char_to_line(char_idx);
let line_start = buf.line_to_char(line_i);
let line_end = buf.line_to_char(line_i + 1);
let line_i = buf.byte_to_line(byte_idx, BUFLINE);
let line_start = buf.line_to_byte(line_i, BUFLINE);
let line_end = buf.line_to_byte(line_i + 1, BUFLINE);
let line = buf.slice(line_start..line_end);
// Find the right block in the line, and the index within that block
let (block_index, block_range) = block_index_and_range(&line, char_idx - line_start);
let (block_index, block_range) = block_index_and_range(&line, byte_idx - line_start);
// Get the right block and an iter into it.
let block = line.slice(block_range.0..block_range.1);
@ -251,7 +252,7 @@ impl LineFormatter {
// Get an appropriate visual block iter.
let vis_iter = self.make_block_vis_iter(g_iter, &line, block_index == 0);
(block, vis_iter, char_idx - (line_start + block_range.0))
(block, vis_iter, byte_idx - (line_start + block_range.0))
}
/// Takes a graphemes iterator for the block, the line its from, and
@ -301,14 +302,14 @@ impl<'a> Iterator for Blocks<'a> {
fn next(&mut self) -> Option<Self::Item> {
// Check if we're done already.
if self.line_idx >= self.buf.len_lines() {
if self.line_idx >= self.buf.len_lines(BUFLINE) {
return None;
}
// Get our return values.
let (iter, is_line_start) = {
let line = self.buf.line(self.line_idx);
let (start, end) = char_range_from_block_index(&line, self.block_idx);
let line = self.buf.line(self.line_idx, BUFLINE);
let (start, end) = byte_range_from_block_index(&line, self.block_idx);
let block = line.slice(start..end);
let iter = self.formatter.make_block_vis_iter(
RopeGraphemes::new(&block),
@ -324,8 +325,8 @@ impl<'a> Iterator for Blocks<'a> {
if self.block_idx >= self.line_block_count {
self.line_idx += 1;
self.block_idx = 0;
if self.line_idx < self.buf.len_lines() {
self.line_block_count = block_count(&self.buf.line(self.line_idx));
if self.line_idx < self.buf.len_lines(BUFLINE) {
self.line_block_count = block_count(&self.buf.line(self.line_idx, BUFLINE));
}
}
@ -390,7 +391,7 @@ impl<'a> BlockVisIter<'a> {
for (g, pos, _) in self {
vpos = pos.0;
i += char_count(&g);
i += g.len();
if i > char_offset {
break;
@ -492,28 +493,28 @@ fn tab_stop_from_vis_pos(pos: usize, tab_width: usize) -> usize {
//--------------------------------------------------------------------------
// Finds the best break at or before the given char index, bounded by
// Finds the best break at or before the given byte index, bounded by
// the given `lower_limit`.
pub fn find_good_break(slice: &RopeSlice, lower_limit: usize, char_idx: usize) -> usize {
pub fn find_good_break(slice: &RopeSlice, lower_limit: usize, byte_idx: usize) -> usize {
const WS_CHARS: &[char] = &[' ', ' ', '\t'];
let slice_len = slice.len_chars();
let char_idx = char_idx.min(slice_len);
let slice_len = slice.len_bytes();
let byte_idx = byte_idx.min(slice_len);
let lower_limit = lower_limit.min(slice_len);
// Early out in trivial cases.
if char_idx < (LINE_BLOCK_LENGTH - LINE_BLOCK_FUDGE) {
return char_idx;
if byte_idx < (LINE_BLOCK_LENGTH - LINE_BLOCK_FUDGE) {
return byte_idx;
}
// Find a whitespace break, if any.
let mut i = char_idx;
let mut i = slice.floor_char_boundary(byte_idx);
let mut prev = if i == slice_len {
None
} else {
Some(slice.char(char_idx))
Some(slice.char_at_byte(byte_idx))
};
let mut char_itr = slice.chars_at(char_idx);
let mut char_itr = slice.chars_at(byte_idx);
while i > lower_limit {
let c = char_itr.prev();
if WS_CHARS.contains(&c.unwrap()) && prev.map(|pc| !WS_CHARS.contains(&pc)).unwrap_or(true)
@ -521,23 +522,23 @@ pub fn find_good_break(slice: &RopeSlice, lower_limit: usize, char_idx: usize) -
return i;
}
prev = c;
i -= 1;
i -= c.unwrap().len_utf8();
}
// Otherwise, at least try to find a grapheme break.
if is_grapheme_boundary(slice, char_idx) {
char_idx
if is_grapheme_boundary(slice, byte_idx) {
byte_idx
} else {
let i = prev_grapheme_boundary(slice, char_idx);
let i = prev_grapheme_boundary(slice, byte_idx);
if i > lower_limit {
i
} else {
char_idx
byte_idx
}
}
}
pub fn char_range_from_block_index(slice: &RopeSlice, block_idx: usize) -> (usize, usize) {
pub fn byte_range_from_block_index(slice: &RopeSlice, block_idx: usize) -> (usize, usize) {
let start = {
let initial = LINE_BLOCK_LENGTH * block_idx;
find_good_break(slice, initial.saturating_sub(LINE_BLOCK_FUDGE), initial)
@ -551,21 +552,21 @@ pub fn char_range_from_block_index(slice: &RopeSlice, block_idx: usize) -> (usiz
(start, end)
}
pub fn block_index_and_range(slice: &RopeSlice, char_idx: usize) -> (usize, (usize, usize)) {
let mut block_index = char_idx / LINE_BLOCK_LENGTH;
let mut range = char_range_from_block_index(slice, block_index);
if char_idx >= range.1 && range.1 < slice.len_chars() {
pub fn block_index_and_range(slice: &RopeSlice, byte_idx: usize) -> (usize, (usize, usize)) {
let mut block_index = byte_idx / LINE_BLOCK_LENGTH;
let mut range = byte_range_from_block_index(slice, block_index);
if byte_idx >= range.1 && range.1 < slice.len_bytes() {
block_index += 1;
range = char_range_from_block_index(slice, block_index);
range = byte_range_from_block_index(slice, block_index);
}
(block_index, range)
}
pub fn block_count(slice: &RopeSlice) -> usize {
let char_count = slice.len_chars();
let mut last_idx = char_count.saturating_sub(1) / LINE_BLOCK_LENGTH;
let byte_count = slice.len_bytes();
let mut last_idx = byte_count.saturating_sub(1) / LINE_BLOCK_LENGTH;
let range = char_range_from_block_index(slice, last_idx + 1);
let range = byte_range_from_block_index(slice, last_idx + 1);
if range.0 < range.1 {
last_idx += 1;
}

View File

@ -1,4 +1,4 @@
use ropey::{iter::Chunks, str_utils::byte_to_char_idx, RopeSlice};
use ropey::{iter::Chunks, RopeSlice};
use unicode_segmentation::{GraphemeCursor, GraphemeIncomplete};
use unicode_width::UnicodeWidthStr;
@ -25,27 +25,24 @@ pub fn grapheme_width(g: &str) -> usize {
}
}
pub fn nth_prev_grapheme_boundary(slice: &RopeSlice, char_idx: usize, n: usize) -> usize {
pub fn nth_prev_grapheme_boundary(slice: &RopeSlice, byte_idx: usize, n: usize) -> usize {
// TODO: implement this more efficiently. This has to do a lot of
// re-scanning of rope chunks. Probably move the main implementation here,
// and have prev_grapheme_boundary call this instead.
let mut char_idx = char_idx;
let mut byte_idx = byte_idx;
for _ in 0..n {
char_idx = prev_grapheme_boundary(slice, char_idx);
byte_idx = prev_grapheme_boundary(slice, byte_idx);
}
char_idx
byte_idx
}
/// Finds the previous grapheme boundary before the given char position.
pub fn prev_grapheme_boundary(slice: &RopeSlice, char_idx: usize) -> usize {
pub fn prev_grapheme_boundary(slice: &RopeSlice, byte_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);
debug_assert!(byte_idx <= slice.len_bytes());
// Get the chunk with our byte index in it.
let (mut chunk, mut chunk_byte_idx, mut chunk_char_idx, _) = slice.chunk_at_byte(byte_idx);
let (mut chunk, mut chunk_byte_idx) = slice.chunk(byte_idx);
// Set up the grapheme cursor.
let mut gc = GraphemeCursor::new(byte_idx, slice.len_bytes(), true);
@ -54,18 +51,14 @@ pub fn prev_grapheme_boundary(slice: &RopeSlice, char_idx: usize) -> usize {
loop {
match gc.prev_boundary(chunk, chunk_byte_idx) {
Ok(None) => return 0,
Ok(Some(n)) => {
let tmp = byte_to_char_idx(chunk, n - chunk_byte_idx);
return chunk_char_idx + tmp;
}
Ok(Some(n)) => return n,
Err(GraphemeIncomplete::PrevChunk) => {
let (a, b, c, _) = slice.chunk_at_byte(chunk_byte_idx - 1);
let (a, b) = slice.chunk(chunk_byte_idx - 1);
chunk = a;
chunk_byte_idx = b;
chunk_char_idx = c;
}
Err(GraphemeIncomplete::PreContext(n)) => {
let ctx_chunk = slice.chunk_at_byte(n - 1).0;
let ctx_chunk = slice.chunk(n - 1).0;
gc.provide_context(ctx_chunk, n - ctx_chunk.len());
}
_ => unreachable!(),
@ -73,27 +66,24 @@ pub fn prev_grapheme_boundary(slice: &RopeSlice, char_idx: usize) -> usize {
}
}
pub fn nth_next_grapheme_boundary(slice: &RopeSlice, char_idx: usize, n: usize) -> usize {
pub fn nth_next_grapheme_boundary(slice: &RopeSlice, byte_idx: usize, n: usize) -> usize {
// TODO: implement this more efficiently. This has to do a lot of
// re-scanning of rope chunks. Probably move the main implementation here,
// and have next_grapheme_boundary call this instead.
let mut char_idx = char_idx;
let mut byte_idx = byte_idx;
for _ in 0..n {
char_idx = next_grapheme_boundary(slice, char_idx);
byte_idx = next_grapheme_boundary(slice, byte_idx);
}
char_idx
byte_idx
}
/// Finds the next grapheme boundary after the given char position.
pub fn next_grapheme_boundary(slice: &RopeSlice, char_idx: usize) -> usize {
pub fn next_grapheme_boundary(slice: &RopeSlice, byte_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);
debug_assert!(byte_idx <= slice.len_bytes());
// Get the chunk with our byte index in it.
let (mut chunk, mut chunk_byte_idx, mut chunk_char_idx, _) = slice.chunk_at_byte(byte_idx);
let (mut chunk, mut chunk_byte_idx) = slice.chunk(byte_idx);
// Set up the grapheme cursor.
let mut gc = GraphemeCursor::new(byte_idx, slice.len_bytes(), true);
@ -101,19 +91,15 @@ pub fn next_grapheme_boundary(slice: &RopeSlice, char_idx: usize) -> usize {
// Find the next grapheme cluster boundary.
loop {
match gc.next_boundary(chunk, chunk_byte_idx) {
Ok(None) => return slice.len_chars(),
Ok(Some(n)) => {
let tmp = byte_to_char_idx(chunk, n - chunk_byte_idx);
return chunk_char_idx + tmp;
}
Ok(None) => return slice.len_bytes(),
Ok(Some(n)) => return n,
Err(GraphemeIncomplete::NextChunk) => {
chunk_byte_idx += chunk.len();
let (a, _, c, _) = slice.chunk_at_byte(chunk_byte_idx);
let (a, _) = slice.chunk(chunk_byte_idx);
chunk = a;
chunk_char_idx = c;
}
Err(GraphemeIncomplete::PreContext(n)) => {
let ctx_chunk = slice.chunk_at_byte(n - 1).0;
let ctx_chunk = slice.chunk(n - 1).0;
gc.provide_context(ctx_chunk, n - ctx_chunk.len());
}
_ => unreachable!(),
@ -122,15 +108,12 @@ pub fn next_grapheme_boundary(slice: &RopeSlice, char_idx: usize) -> usize {
}
/// Returns whether the given char position is a grapheme boundary.
pub fn is_grapheme_boundary(slice: &RopeSlice, char_idx: usize) -> bool {
pub fn is_grapheme_boundary(slice: &RopeSlice, byte_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);
debug_assert!(byte_idx <= slice.len_bytes());
// Get the chunk with our byte index in it.
let (chunk, chunk_byte_idx, _, _) = slice.chunk_at_byte(byte_idx);
let (chunk, chunk_byte_idx) = slice.chunk(byte_idx);
// Set up the grapheme cursor.
let mut gc = GraphemeCursor::new(byte_idx, slice.len_bytes(), true);
@ -140,7 +123,7 @@ pub fn is_grapheme_boundary(slice: &RopeSlice, char_idx: usize) -> bool {
match gc.is_boundary(chunk, chunk_byte_idx) {
Ok(n) => return n,
Err(GraphemeIncomplete::PreContext(n)) => {
let (ctx_chunk, ctx_byte_start, _, _) = slice.chunk_at_byte(n - 1);
let (ctx_chunk, ctx_byte_start) = slice.chunk(n - 1);
gc.provide_context(ctx_chunk, ctx_byte_start);
}
_ => unreachable!(),
@ -198,15 +181,6 @@ impl<'a> Iterator for RopeGraphemes<'a> {
}
}
if a < self.cur_chunk_start {
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))
} else {
let a2 = a - self.cur_chunk_start;
let b2 = b - self.cur_chunk_start;
Some((&self.cur_chunk[a2..b2]).into())
}
Some(self.text.slice(a..b))
}
}

View File

@ -1,6 +1,6 @@
//! Misc helpful utility functions for TextBuffer related stuff.
use ropey::{str_utils::byte_to_char_idx, RopeSlice};
use ropey::RopeSlice;
pub fn is_line_ending(text: &str) -> bool {
match text.chars().nth(0) {
@ -44,10 +44,6 @@ pub fn is_whitespace(c: char) -> bool {
}
}
pub fn char_count(text: &str) -> usize {
byte_to_char_idx(text, text.len())
}
/// Represents one of the valid Unicode line endings.
/// Also acts as an index into `LINE_ENDINGS`.
#[derive(PartialEq, Copy, Clone)]
@ -79,7 +75,7 @@ pub fn str_to_line_ending(g: &str) -> LineEnding {
}
}
pub fn rope_slice_to_line_ending(g: &RopeSlice) -> LineEnding {
pub fn rope_slice_to_line_ending(g: RopeSlice) -> LineEnding {
if let Some(text) = g.as_str() {
str_to_line_ending(text)
} else if g == "\u{000D}\u{000A}" {
@ -107,21 +103,3 @@ pub const LINE_ENDINGS: [&'static str; 9] = [
"\u{2028}",
"\u{2029}",
];
//--------------------------------------------------------------------------
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn char_count_1() {
let text_0 = "";
let text_1 = "Hello world!";
let text_2 = "今日はみんなさん!";
assert_eq!(0, char_count(text_0));
assert_eq!(12, char_count(text_1));
assert_eq!(9, char_count(text_2));
}
}

View File

@ -10,11 +10,11 @@ use crossterm::{
style::Color,
};
use backend::buffer::BufferPath;
use backend::buffer::{BufferPath, BUFLINE};
use crate::{
editor::Editor,
string_utils::{char_count, is_line_ending, line_ending_to_str, LineEnding},
string_utils::{is_line_ending, line_ending_to_str, LineEnding},
utils::{digit_count, Timer},
};
@ -500,9 +500,9 @@ impl TermUI {
// Percentage position in document
// TODO: use view instead of cursor for calculation if there is more
// than one cursor.
let percentage: usize = if editor.buffer.text.len_chars() > 0 {
let percentage: usize = if editor.buffer.text.len_bytes() > 0 {
(((editor.buffer.mark_sets[editor.c_msi].main().unwrap().head as f32)
/ (editor.buffer.text.len_chars() as f32))
/ (editor.buffer.text.len_bytes() as f32))
* 100.0) as usize
} else {
100
@ -546,11 +546,11 @@ impl TermUI {
// Calculate all the starting info
let gutter_width = editor.editor_dim.1 - editor.view_dim.1;
let blank_gutter = &" "[..gutter_width - 1];
let line_index = editor.buffer.text.char_to_line(view_pos);
let line_index = editor.buffer.text.byte_to_line(view_pos, BUFLINE);
let (blocks_iter, char_offset) = editor.formatter.iter(&editor.buffer.text, view_pos);
let (blocks_iter, byte_offset) = editor.formatter.iter(&editor.buffer.text, view_pos);
let vis_line_offset = blocks_iter.clone().next().unwrap().0.vpos(char_offset);
let vis_line_offset = blocks_iter.clone().next().unwrap().0.vpos(byte_offset);
let mut screen_line = c1.0 as isize - vis_line_offset as isize;
let screen_col = c1.1 as isize + gutter_width as isize;
@ -570,7 +570,7 @@ impl TermUI {
// Loop through the blocks, printing them to the screen.
let mut is_first_loop = true;
let mut line_num = line_index + 1;
let mut char_index = view_pos - char_offset;
let mut byte_index = view_pos - byte_offset;
for (block_vis_iter, is_line_start) in blocks_iter {
if is_line_start && !is_first_loop {
line_num += 1;
@ -624,7 +624,7 @@ impl TermUI {
// Check if the character is within a cursor
let mut at_cursor = false;
for c in cursors.iter() {
if char_index >= c.range().start && char_index <= c.range().end {
if byte_index >= c.range().start && byte_index <= c.range().end {
at_cursor = true;
self.screen.set_cursor(px as usize, py as usize);
}
@ -657,7 +657,7 @@ impl TermUI {
}
}
char_index += char_count(&g);
byte_index += g.len();
}
screen_line += 1;
@ -670,7 +670,7 @@ impl TermUI {
// Check if the character is within a cursor
let mut at_cursor = false;
for c in cursors.iter() {
if char_index >= c.range().start && char_index <= c.range().end {
if byte_index >= c.range().start && byte_index <= c.range().end {
at_cursor = true;
}
}
@ -679,7 +679,7 @@ impl TermUI {
// Calculate the cell coordinates at which to draw the cursor
let pos_x = editor.formatter.get_horizontal(
&self.editor.buffer.text,
self.editor.buffer.text.len_chars(),
self.editor.buffer.text.len_bytes(),
);
let mut px = pos_x as isize + screen_col;
let mut py = screen_line - 1;

View File

@ -10,7 +10,7 @@ name = "backend"
path = "src/lib.rs"
[dependencies]
ropey = "1"
ropey = { git = "https://github.com/cessen/ropey", branch = "2.0-alpha" }
tenthash = "0.2"
unicode-segmentation = "1.7"

View File

@ -5,10 +5,13 @@ use std::{
path::PathBuf,
};
use ropey::Rope;
use ropey::{LineType, Rope};
use crate::{history::History, marks::MarkSet, transaction::Transaction};
/// The line type that should be used as the standard line metric in a buffer.
pub const BUFLINE: LineType = LineType::LF_CR;
/// A path for an open text buffer.
///
/// This indicates where the text data of the buffer came from, and
@ -67,7 +70,7 @@ impl Buffer {
///
/// The range does not have to be ordered (i.e. the first component can be
/// greater than the second).
pub fn edit(&mut self, char_idx_range: (usize, usize), text: &str) {
pub fn edit(&mut self, byte_idx_range: (usize, usize), text: &str) {
if self.edits_since_saved >= 0 {
self.edits_since_saved = self.edits_since_saved.wrapping_add(1);
} else {
@ -77,18 +80,14 @@ impl Buffer {
}
// Get the range, properly ordered.
let (start, end) = if char_idx_range.0 < char_idx_range.1 {
(char_idx_range.0, char_idx_range.1)
let (start, end) = if byte_idx_range.0 < byte_idx_range.1 {
(byte_idx_range.0, byte_idx_range.1)
} else {
(char_idx_range.1, char_idx_range.0)
(byte_idx_range.1, byte_idx_range.0)
};
// Build the transaction.
let trans = Transaction::from_edit(
self.text.char_to_byte(start),
&Cow::from(self.text.slice(start..end)),
text,
);
let trans = Transaction::from_edit(start, &Cow::from(self.text.slice(start..end)), text);
// Update mark sets.
for mark_set in self.mark_sets.iter_mut() {

View File

@ -306,14 +306,12 @@ impl Transaction {
let old = &self.buffer[old.0..old.1];
let new = &self.buffer[new.0..new.1];
let char_i = text.byte_to_char(i);
if !old.is_empty() {
let old_char_len = old.chars().count();
debug_assert_eq!(text.slice(char_i..(char_i + old_char_len)), old);
text.remove(char_i..(char_i + old_char_len));
debug_assert_eq!(text.slice(i..(i + old.len())), old);
text.remove(i..(i + old.len()));
}
if !new.is_empty() {
text.insert(char_i, new);
text.insert(i, new);
}
i = i + new.len();
@ -341,14 +339,12 @@ impl Transaction {
let old = &self.buffer[new_range.0..new_range.1];
let new = &self.buffer[old_range.0..old_range.1];
let char_i = text.byte_to_char(i);
if !old.is_empty() {
let old_char_len = old.chars().count();
debug_assert_eq!(text.slice(char_i..(char_i + old_char_len)), old);
text.remove(char_i..(char_i + old_char_len));
debug_assert_eq!(text.slice(i..(i + old.len())), old);
text.remove(i..(i + old.len()));
}
if !new.is_empty() {
text.insert(char_i, new);
text.insert(i, new);
}
i = i + new.len();