From cd2314905a771b03052348181bddd7cd5954711f Mon Sep 17 00:00:00 2001 From: Nathan Vegdahl Date: Sat, 8 Jun 2024 07:26:03 +0200 Subject: [PATCH] Port to Ropey 2.0-alpha. --- Cargo.lock | 6 +- Cargo.toml | 4 +- src/editor/mod.rs | 40 +++--- src/formatter.rs | 179 +++++++++++++------------- src/graphemes.rs | 80 ++++-------- src/string_utils.rs | 26 +--- src/term_ui/mod.rs | 24 ++-- sub_crates/backend/Cargo.toml | 2 +- sub_crates/backend/src/buffer.rs | 19 ++- sub_crates/backend/src/transaction.rs | 16 +-- 10 files changed, 168 insertions(+), 228 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ee81486..7bec6ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", ] diff --git a/Cargo.toml b/Cargo.toml index cd02fcd..97711e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/editor/mod.rs b/src/editor/mod.rs index 8c88373..1eb7da3 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -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, diff --git a/src/formatter.rs b/src/formatter.rs index e940c89..39d47d8 100644 --- a/src/formatter.rs +++ b/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 { // 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; } diff --git a/src/graphemes.rs b/src/graphemes.rs index 891018a..39e95d8 100644 --- a/src/graphemes.rs +++ b/src/graphemes.rs @@ -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)) } } diff --git a/src/string_utils.rs b/src/string_utils.rs index ff79c3b..09b087d 100644 --- a/src/string_utils.rs +++ b/src/string_utils.rs @@ -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)); - } -} diff --git a/src/term_ui/mod.rs b/src/term_ui/mod.rs index c316867..f72862f 100644 --- a/src/term_ui/mod.rs +++ b/src/term_ui/mod.rs @@ -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; diff --git a/sub_crates/backend/Cargo.toml b/sub_crates/backend/Cargo.toml index 07ab5a7..d6caf38 100644 --- a/sub_crates/backend/Cargo.toml +++ b/sub_crates/backend/Cargo.toml @@ -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" diff --git a/sub_crates/backend/src/buffer.rs b/sub_crates/backend/src/buffer.rs index a7f28fa..25698a3 100644 --- a/sub_crates/backend/src/buffer.rs +++ b/sub_crates/backend/src/buffer.rs @@ -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() { diff --git a/sub_crates/backend/src/transaction.rs b/sub_crates/backend/src/transaction.rs index 4694156..1d767d6 100644 --- a/sub_crates/backend/src/transaction.rs +++ b/sub_crates/backend/src/transaction.rs @@ -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();