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]] [[package]]
name = "ropey" name = "ropey"
version = "1.6.1" version = "2.0.0-alpha"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://github.com/cessen/ropey?branch=2.0-alpha#013c3cf6d3c2647b79af3f346fff7610efe37c5a"
checksum = "93411e420bcd1a75ddd1dc3caf18c23155eda2c090631a85af21ba19e97093b5"
dependencies = [ dependencies = [
"smallvec",
"str_indices", "str_indices",
] ]

View File

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

View File

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

View File

@ -2,9 +2,10 @@ use std::borrow::Cow;
use ropey::{Rope, RopeSlice}; use ropey::{Rope, RopeSlice};
use backend::buffer::BUFLINE;
use crate::{ use crate::{
graphemes::{grapheme_width, is_grapheme_boundary, prev_grapheme_boundary, RopeGraphemes}, graphemes::{grapheme_width, is_grapheme_boundary, prev_grapheme_boundary, RopeGraphemes},
string_utils::char_count,
string_utils::str_is_whitespace, string_utils::str_is_whitespace,
}; };
@ -39,16 +40,16 @@ impl LineFormatter {
} }
/// Returns an iterator over the blocks of the buffer, starting at the /// 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. /// 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. // Get the line.
let (line_i, col_i) = { let (line_i, col_i) = {
let line_idx = buf.char_to_line(char_idx); let line_idx = buf.byte_to_line(byte_idx, BUFLINE);
let col_idx = char_idx - buf.line_to_char(line_idx); let col_idx = byte_idx - buf.line_to_byte(line_idx, BUFLINE);
(line_idx, col_idx) (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 // 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); 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. /// Converts from byte index to its formatted horizontal 2d position.
pub fn get_horizontal(&self, buf: &Rope, char_idx: usize) -> usize { pub fn get_horizontal(&self, buf: &Rope, byte_idx: usize) -> usize {
let (_, vis_iter, char_offset) = self.block_vis_iter_and_char_offset(buf, char_idx); 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 // Traverse the iterator and find the horizontal position of the char
// index. // index.
@ -79,9 +80,9 @@ impl LineFormatter {
for (g, pos, width) in vis_iter { for (g, pos, width) in vis_iter {
hpos = pos.1; hpos = pos.1;
last_width = width; last_width = width;
i += char_count(&g); i += g.len();
if i > char_offset { if i > byte_offset {
return hpos; return hpos;
} }
} }
@ -91,14 +92,14 @@ impl LineFormatter {
return hpos + last_width; return hpos + last_width;
} }
/// Takes a char index and a desired visual horizontal position, and /// Takes a byte index and a desired visual horizontal position, and
/// returns a char index on the same visual line as the given index, /// 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 /// but offset to have the desired horizontal position (or as close as is
/// possible. /// possible.
pub fn set_horizontal(&self, buf: &Rope, char_idx: usize, horizontal: usize) -> usize { pub fn set_horizontal(&self, buf: &Rope, byte_idx: usize, horizontal: usize) -> usize {
let (_, vis_iter, char_offset) = self.block_vis_iter_and_char_offset(buf, char_idx); 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 i = 0;
let mut last_i = 0; let mut last_i = 0;
let mut last_pos = (0, 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 // that means the target was on the previous line but the line
// wasn't long enough, so return the index of the last grapheme // wasn't long enough, so return the index of the last grapheme
// of the previous line. // of the previous line.
if i > char_offset { if i > byte_offset {
return char_idx - char_offset + last_i; return byte_idx - byte_offset + last_i;
} }
// Otherwise reset and keep going. // Otherwise reset and keep going.
hpos_char_idx = None; hpos_byte_idx = None;
} }
// Check if we found the horizontal position on this line, // Check if we found the horizontal position on this line,
// and set it if so. // and set it if so.
if hpos_char_idx == None && (pos.1 + width) > horizontal { if hpos_byte_idx == None && (pos.1 + width) > horizontal {
hpos_char_idx = Some(i); hpos_byte_idx = Some(i);
} }
// Check if we've found the horizontal position _and_ the passed // Check if we've found the horizontal position _and_ the passed
// char_idx on the same line, and return if so. // byte_idx on the same line, and return if so.
if (i + char_count(&g)) > char_offset && hpos_char_idx != None { if (i + g.len()) > byte_offset && hpos_byte_idx != None {
return char_idx - char_offset + hpos_char_idx.unwrap(); return byte_idx - byte_offset + hpos_byte_idx.unwrap();
} }
last_pos = pos; last_pos = pos;
last_i = i; last_i = i;
i += char_count(&g); i += g.len();
} }
// If we reached the end of the text, return the last char index. // If we reached the end of the text, return the last char index.
let end_i = char_idx - char_offset + i; let end_i = byte_idx - byte_offset + i;
let end_last_i = char_idx - char_offset + last_i; let end_last_i = byte_idx - byte_offset + last_i;
if buf.len_chars() == end_i { if buf.len_bytes() == end_i {
return end_i; return end_i;
} else { } else {
return end_last_i; 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. /// index after that visual offset is applied.
pub fn offset_vertical(&self, buf: &Rope, char_idx: usize, v_offset: isize) -> usize { pub fn offset_vertical(&self, buf: &Rope, byte_idx: usize, v_offset: isize) -> usize {
let mut char_idx = char_idx; let mut byte_idx = byte_idx;
let mut v_offset = v_offset; let mut v_offset = v_offset;
while v_offset != 0 { while v_offset != 0 {
// Get our block and the offset of the char inside it. // Get our block and the offset of the byte inside it.
let (block, block_vis_iter, char_offset) = let (block, block_vis_iter, byte_offset) =
self.block_vis_iter_and_char_offset(buf, char_idx); self.block_vis_iter_and_byte_offset(buf, byte_idx);
// Get the vertical size of the block and the vertical // 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 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. // 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, // Check if the offset position is within the block or not,
// and handle appropriately. // and handle appropriately.
if offset_char_v_pos < 0 { if offset_byte_v_pos < 0 {
// If we're off the start of the block. // If we're off the start of the block.
char_idx = char_idx.saturating_sub(char_offset + 1); byte_idx = byte_idx.saturating_sub(byte_offset + 1);
v_offset += char_v_pos as isize + 1; v_offset += byte_v_pos as isize + 1;
if char_idx == 0 { if byte_idx == 0 {
break; 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. // If we're off the end of the block.
char_idx = (char_idx + block.len_chars() - char_offset).min(buf.len_chars()); byte_idx = (byte_idx + block.len_bytes() - byte_offset).min(buf.len_bytes());
v_offset -= block_v_dim as isize - char_v_pos as isize; v_offset -= block_v_dim as isize - byte_v_pos as isize;
if char_idx == buf.len_chars() { if byte_idx == buf.len_bytes() {
break; break;
} }
} else { } else {
@ -184,18 +185,18 @@ impl LineFormatter {
// appropriate char index and return. // appropriate char index and return.
let mut i = 0; let mut i = 0;
for (g, pos, _) in block_vis_iter { 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; break;
} }
i += char_count(&g); i += g.len();
} }
char_idx -= char_offset; byte_idx -= byte_offset;
char_idx += i; byte_idx += i;
v_offset = 0; v_offset = 0;
} }
} }
return char_idx; return byte_idx;
} }
//---------------------------------------------------- //----------------------------------------------------
@ -229,20 +230,20 @@ impl LineFormatter {
indent indent
} }
/// Returns the appropriate BlockVisIter containing the given char, and the /// Returns the appropriate BlockVisIter containing the given byte, and the
/// char's offset within that iter. /// byte's offset within that iter.
fn block_vis_iter_and_char_offset<'b>( fn block_vis_iter_and_byte_offset<'b>(
&self, &self,
buf: &'b Rope, buf: &'b Rope,
char_idx: usize, byte_idx: usize,
) -> (RopeSlice<'b>, BlockVisIter<'b>, usize) { ) -> (RopeSlice<'b>, BlockVisIter<'b>, usize) {
let line_i = buf.char_to_line(char_idx); let line_i = buf.byte_to_line(byte_idx, BUFLINE);
let line_start = buf.line_to_char(line_i); let line_start = buf.line_to_byte(line_i, BUFLINE);
let line_end = buf.line_to_char(line_i + 1); let line_end = buf.line_to_byte(line_i + 1, BUFLINE);
let line = buf.slice(line_start..line_end); let line = buf.slice(line_start..line_end);
// 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 (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. // Get the right block and an iter into it.
let block = line.slice(block_range.0..block_range.1); let block = line.slice(block_range.0..block_range.1);
@ -251,7 +252,7 @@ impl LineFormatter {
// Get an appropriate visual block iter. // Get an appropriate visual block iter.
let vis_iter = self.make_block_vis_iter(g_iter, &line, block_index == 0); 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 /// 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> { fn next(&mut self) -> Option<Self::Item> {
// Check if we're done already. // 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; return None;
} }
// Get our return values. // Get our return values.
let (iter, is_line_start) = { let (iter, is_line_start) = {
let line = self.buf.line(self.line_idx); let line = self.buf.line(self.line_idx, BUFLINE);
let (start, end) = char_range_from_block_index(&line, self.block_idx); let (start, end) = byte_range_from_block_index(&line, self.block_idx);
let block = line.slice(start..end); let block = line.slice(start..end);
let iter = self.formatter.make_block_vis_iter( let iter = self.formatter.make_block_vis_iter(
RopeGraphemes::new(&block), RopeGraphemes::new(&block),
@ -324,8 +325,8 @@ impl<'a> Iterator for Blocks<'a> {
if self.block_idx >= self.line_block_count { if self.block_idx >= self.line_block_count {
self.line_idx += 1; self.line_idx += 1;
self.block_idx = 0; self.block_idx = 0;
if self.line_idx < self.buf.len_lines() { if self.line_idx < self.buf.len_lines(BUFLINE) {
self.line_block_count = block_count(&self.buf.line(self.line_idx)); 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 { for (g, pos, _) in self {
vpos = pos.0; vpos = pos.0;
i += char_count(&g); i += g.len();
if i > char_offset { if i > char_offset {
break; 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`. // 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']; const WS_CHARS: &[char] = &[' ', ' ', '\t'];
let slice_len = slice.len_chars(); let slice_len = slice.len_bytes();
let char_idx = char_idx.min(slice_len); let byte_idx = byte_idx.min(slice_len);
let lower_limit = lower_limit.min(slice_len); let lower_limit = lower_limit.min(slice_len);
// Early out in trivial cases. // Early out in trivial cases.
if char_idx < (LINE_BLOCK_LENGTH - LINE_BLOCK_FUDGE) { if byte_idx < (LINE_BLOCK_LENGTH - LINE_BLOCK_FUDGE) {
return char_idx; return byte_idx;
} }
// Find a whitespace break, if any. // 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 { let mut prev = if i == slice_len {
None None
} else { } 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 { while i > lower_limit {
let c = char_itr.prev(); let c = char_itr.prev();
if WS_CHARS.contains(&c.unwrap()) && prev.map(|pc| !WS_CHARS.contains(&pc)).unwrap_or(true) 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; return i;
} }
prev = c; prev = c;
i -= 1; i -= c.unwrap().len_utf8();
} }
// Otherwise, at least try to find a grapheme break. // Otherwise, at least try to find a grapheme break.
if is_grapheme_boundary(slice, char_idx) { if is_grapheme_boundary(slice, byte_idx) {
char_idx byte_idx
} else { } else {
let i = prev_grapheme_boundary(slice, char_idx); let i = prev_grapheme_boundary(slice, byte_idx);
if i > lower_limit { if i > lower_limit {
i i
} else { } 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 start = {
let initial = LINE_BLOCK_LENGTH * block_idx; let initial = LINE_BLOCK_LENGTH * block_idx;
find_good_break(slice, initial.saturating_sub(LINE_BLOCK_FUDGE), initial) 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) (start, end)
} }
pub fn block_index_and_range(slice: &RopeSlice, char_idx: usize) -> (usize, (usize, usize)) { pub fn block_index_and_range(slice: &RopeSlice, byte_idx: usize) -> (usize, (usize, usize)) {
let mut block_index = char_idx / LINE_BLOCK_LENGTH; let mut block_index = byte_idx / LINE_BLOCK_LENGTH;
let mut range = char_range_from_block_index(slice, block_index); let mut range = byte_range_from_block_index(slice, block_index);
if char_idx >= range.1 && range.1 < slice.len_chars() { if byte_idx >= range.1 && range.1 < slice.len_bytes() {
block_index += 1; block_index += 1;
range = char_range_from_block_index(slice, block_index); range = byte_range_from_block_index(slice, block_index);
} }
(block_index, range) (block_index, range)
} }
pub fn block_count(slice: &RopeSlice) -> usize { pub fn block_count(slice: &RopeSlice) -> usize {
let char_count = slice.len_chars(); let byte_count = slice.len_bytes();
let mut last_idx = char_count.saturating_sub(1) / LINE_BLOCK_LENGTH; 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 { if range.0 < range.1 {
last_idx += 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_segmentation::{GraphemeCursor, GraphemeIncomplete};
use unicode_width::UnicodeWidthStr; 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 // TODO: implement this more efficiently. This has to do a lot of
// re-scanning of rope chunks. Probably move the main implementation here, // re-scanning of rope chunks. Probably move the main implementation here,
// and have prev_grapheme_boundary call this instead. // and have prev_grapheme_boundary call this instead.
let mut char_idx = char_idx; let mut byte_idx = byte_idx;
for _ in 0..n { 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. /// 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 // Bounds check
debug_assert!(char_idx <= slice.len_chars()); debug_assert!(byte_idx <= slice.len_bytes());
// 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. // 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. // Set up the grapheme cursor.
let mut gc = GraphemeCursor::new(byte_idx, slice.len_bytes(), true); 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 { loop {
match gc.prev_boundary(chunk, chunk_byte_idx) { match gc.prev_boundary(chunk, chunk_byte_idx) {
Ok(None) => return 0, Ok(None) => return 0,
Ok(Some(n)) => { Ok(Some(n)) => return n,
let tmp = byte_to_char_idx(chunk, n - chunk_byte_idx);
return chunk_char_idx + tmp;
}
Err(GraphemeIncomplete::PrevChunk) => { 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 = a;
chunk_byte_idx = b; chunk_byte_idx = b;
chunk_char_idx = c;
} }
Err(GraphemeIncomplete::PreContext(n)) => { 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()); gc.provide_context(ctx_chunk, n - ctx_chunk.len());
} }
_ => unreachable!(), _ => 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 // TODO: implement this more efficiently. This has to do a lot of
// re-scanning of rope chunks. Probably move the main implementation here, // re-scanning of rope chunks. Probably move the main implementation here,
// and have next_grapheme_boundary call this instead. // and have next_grapheme_boundary call this instead.
let mut char_idx = char_idx; let mut byte_idx = byte_idx;
for _ in 0..n { 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. /// 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 // Bounds check
debug_assert!(char_idx <= slice.len_chars()); debug_assert!(byte_idx <= slice.len_bytes());
// 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. // 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. // Set up the grapheme cursor.
let mut gc = GraphemeCursor::new(byte_idx, slice.len_bytes(), true); 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. // Find the next grapheme cluster boundary.
loop { loop {
match gc.next_boundary(chunk, chunk_byte_idx) { match gc.next_boundary(chunk, chunk_byte_idx) {
Ok(None) => return slice.len_chars(), Ok(None) => return slice.len_bytes(),
Ok(Some(n)) => { Ok(Some(n)) => return n,
let tmp = byte_to_char_idx(chunk, n - chunk_byte_idx);
return chunk_char_idx + tmp;
}
Err(GraphemeIncomplete::NextChunk) => { Err(GraphemeIncomplete::NextChunk) => {
chunk_byte_idx += chunk.len(); 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 = a;
chunk_char_idx = c;
} }
Err(GraphemeIncomplete::PreContext(n)) => { 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()); gc.provide_context(ctx_chunk, n - ctx_chunk.len());
} }
_ => unreachable!(), _ => 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. /// 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 // Bounds check
debug_assert!(char_idx <= slice.len_chars()); debug_assert!(byte_idx <= slice.len_bytes());
// 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. // 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. // Set up the grapheme cursor.
let mut gc = GraphemeCursor::new(byte_idx, slice.len_bytes(), true); 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) { match gc.is_boundary(chunk, chunk_byte_idx) {
Ok(n) => return n, Ok(n) => return n,
Err(GraphemeIncomplete::PreContext(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); gc.provide_context(ctx_chunk, ctx_byte_start);
} }
_ => unreachable!(), _ => unreachable!(),
@ -198,15 +181,6 @@ impl<'a> Iterator for RopeGraphemes<'a> {
} }
} }
if a < self.cur_chunk_start { Some(self.text.slice(a..b))
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())
}
} }
} }

View File

@ -1,6 +1,6 @@
//! Misc helpful utility functions for TextBuffer related stuff. //! 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 { pub fn is_line_ending(text: &str) -> bool {
match text.chars().nth(0) { 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. /// 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)]
@ -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() { if let Some(text) = g.as_str() {
str_to_line_ending(text) str_to_line_ending(text)
} else if g == "\u{000D}\u{000A}" { } else if g == "\u{000D}\u{000A}" {
@ -107,21 +103,3 @@ pub const LINE_ENDINGS: [&'static str; 9] = [
"\u{2028}", "\u{2028}",
"\u{2029}", "\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, style::Color,
}; };
use backend::buffer::BufferPath; use backend::buffer::{BufferPath, BUFLINE};
use crate::{ use crate::{
editor::Editor, 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}, utils::{digit_count, Timer},
}; };
@ -500,9 +500,9 @@ impl TermUI {
// 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.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.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 * 100.0) as usize
} else { } else {
100 100
@ -546,11 +546,11 @@ impl TermUI {
// 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 blank_gutter = &" "[..gutter_width - 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 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;
@ -570,7 +570,7 @@ impl TermUI {
// Loop through the blocks, printing them to the screen. // Loop through the blocks, printing them to the screen.
let mut is_first_loop = true; let mut is_first_loop = true;
let mut line_num = line_index + 1; 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 { for (block_vis_iter, is_line_start) in blocks_iter {
if is_line_start && !is_first_loop { if is_line_start && !is_first_loop {
line_num += 1; line_num += 1;
@ -624,7 +624,7 @@ impl TermUI {
// Check if the character is within a cursor // Check if the character is within a cursor
let mut at_cursor = false; let mut at_cursor = false;
for c in cursors.iter() { 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; at_cursor = true;
self.screen.set_cursor(px as usize, py as usize); 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; screen_line += 1;
@ -670,7 +670,7 @@ impl TermUI {
// Check if the character is within a cursor // Check if the character is within a cursor
let mut at_cursor = false; let mut at_cursor = false;
for c in cursors.iter() { 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; at_cursor = true;
} }
} }
@ -679,7 +679,7 @@ impl TermUI {
// 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.get_horizontal( let pos_x = editor.formatter.get_horizontal(
&self.editor.buffer.text, &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 px = pos_x as isize + screen_col;
let mut py = screen_line - 1; let mut py = screen_line - 1;

View File

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

View File

@ -5,10 +5,13 @@ use std::{
path::PathBuf, path::PathBuf,
}; };
use ropey::Rope; use ropey::{LineType, Rope};
use crate::{history::History, marks::MarkSet, transaction::Transaction}; 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. /// A path for an open text buffer.
/// ///
/// This indicates where the text data of the buffer came from, and /// 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 /// The range does not have to be ordered (i.e. the first component can be
/// greater than the second). /// 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 { if self.edits_since_saved >= 0 {
self.edits_since_saved = self.edits_since_saved.wrapping_add(1); self.edits_since_saved = self.edits_since_saved.wrapping_add(1);
} else { } else {
@ -77,18 +80,14 @@ impl Buffer {
} }
// Get the range, properly ordered. // Get the range, properly ordered.
let (start, end) = if char_idx_range.0 < char_idx_range.1 { let (start, end) = if byte_idx_range.0 < byte_idx_range.1 {
(char_idx_range.0, char_idx_range.1) (byte_idx_range.0, byte_idx_range.1)
} else { } else {
(char_idx_range.1, char_idx_range.0) (byte_idx_range.1, byte_idx_range.0)
}; };
// Build the transaction. // Build the transaction.
let trans = Transaction::from_edit( let trans = Transaction::from_edit(start, &Cow::from(self.text.slice(start..end)), text);
self.text.char_to_byte(start),
&Cow::from(self.text.slice(start..end)),
text,
);
// Update mark sets. // Update mark sets.
for mark_set in self.mark_sets.iter_mut() { 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 old = &self.buffer[old.0..old.1];
let new = &self.buffer[new.0..new.1]; let new = &self.buffer[new.0..new.1];
let char_i = text.byte_to_char(i);
if !old.is_empty() { if !old.is_empty() {
let old_char_len = old.chars().count(); debug_assert_eq!(text.slice(i..(i + old.len())), old);
debug_assert_eq!(text.slice(char_i..(char_i + old_char_len)), old); text.remove(i..(i + old.len()));
text.remove(char_i..(char_i + old_char_len));
} }
if !new.is_empty() { if !new.is_empty() {
text.insert(char_i, new); text.insert(i, new);
} }
i = i + new.len(); i = i + new.len();
@ -341,14 +339,12 @@ impl Transaction {
let old = &self.buffer[new_range.0..new_range.1]; let old = &self.buffer[new_range.0..new_range.1];
let new = &self.buffer[old_range.0..old_range.1]; let new = &self.buffer[old_range.0..old_range.1];
let char_i = text.byte_to_char(i);
if !old.is_empty() { if !old.is_empty() {
let old_char_len = old.chars().count(); debug_assert_eq!(text.slice(i..(i + old.len())), old);
debug_assert_eq!(text.slice(char_i..(char_i + old_char_len)), old); text.remove(i..(i + old.len()));
text.remove(char_i..(char_i + old_char_len));
} }
if !new.is_empty() { if !new.is_empty() {
text.insert(char_i, new); text.insert(i, new);
} }
i = i + new.len(); i = i + new.len();