Port to Ropey 2.0-alpha.
This commit is contained in:
parent
b1391ebb35
commit
cd2314905a
6
Cargo.lock
generated
6
Cargo.lock
generated
|
@ -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",
|
||||
]
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
|
|
179
src/formatter.rs
179
src/formatter.rs
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue
Block a user