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]]
|
[[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",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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,
|
||||||
|
|
179
src/formatter.rs
179
src/formatter.rs
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
Loading…
Reference in New Issue
Block a user