diff --git a/src/formatter.rs b/src/formatter.rs index e6fba44..077deb9 100644 --- a/src/formatter.rs +++ b/src/formatter.rs @@ -29,26 +29,20 @@ pub trait LineFormatter { /// Returns the 2d visual dimensions of the given text when formatted /// by the formatter. /// The text to be formatted is passed as a grapheme iterator. - fn dimensions<'a, T>(&'a self, g_iter: T) -> (usize, usize) - where - T: Iterator>; + fn dimensions(&self, g_iter: RopeGraphemes) -> (usize, usize); /// Converts a char index within a text into a visual 2d position. /// The text to be formatted is passed as a grapheme iterator. - fn index_to_v2d<'a, T>(&'a self, g_iter: T, char_idx: usize) -> (usize, usize) - where - T: Iterator>; + fn index_to_v2d(&self, g_iter: RopeGraphemes, char_idx: usize) -> (usize, usize); /// Converts a visual 2d position into a char index within a text. /// The text to be formatted is passed as a grapheme iterator. - fn v2d_to_index<'a, T>( - &'a self, - g_iter: T, + fn v2d_to_index( + &self, + g_iter: RopeGraphemes, v2d: (usize, usize), rounding: (RoundingBehavior, RoundingBehavior), - ) -> usize - where - T: Iterator>; + ) -> usize; /// Converts from char index to the horizontal 2d char index. fn index_to_horizontal_v2d(&self, buf: &Buffer, char_idx: usize) -> usize { diff --git a/src/string_utils.rs b/src/string_utils.rs index 26b3d73..eeabb8e 100644 --- a/src/string_utils.rs +++ b/src/string_utils.rs @@ -16,7 +16,11 @@ pub fn is_line_ending(text: &str) -> bool { } pub fn rope_slice_is_line_ending(text: &RopeSlice) -> bool { - rope_slice_to_line_ending(text) != LineEnding::None + match text.char(0) { + c if (c >= '\u{000A}' && c <= '\u{000D}') => true, + '\u{0085}' | '\u{2028}' | '\u{2029}' => true, + _ => false, + } } pub fn is_whitespace(text: &str) -> bool { diff --git a/src/term_ui/formatter.rs b/src/term_ui/formatter.rs index 211b0db..d91d9ae 100644 --- a/src/term_ui/formatter.rs +++ b/src/term_ui/formatter.rs @@ -1,11 +1,9 @@ -use std::cmp::max; - -use ropey::RopeSlice; +use std::{borrow::Cow, cmp::max}; use crate::{ formatter::{LineFormatter, RoundingBehavior}, - string_utils::{rope_slice_is_line_ending, rope_slice_is_whitespace}, - utils::grapheme_width, + string_utils::{is_line_ending, is_whitespace}, + utils::{grapheme_width, RopeGraphemes}, }; pub enum WrapType { @@ -49,27 +47,24 @@ impl ConsoleLineFormatter { } } - pub fn iter<'a, T>(&'a self, g_iter: T) -> ConsoleLineFormatterVisIter<'a, T> - where - T: Iterator>, - { - ConsoleLineFormatterVisIter::<'a, T> { - grapheme_iter: g_iter, - f: self, - pos: (0, 0), - indent: 0, - indent_found: false, + pub fn iter<'a>(&self, g_iter: RopeGraphemes<'a>) -> FormattingIter<'a> { + FormattingIter { + grapheme_itr: g_iter, + wrap_width: match self.wrap_type { + WrapType::WordWrap(w) => w, + WrapType::CharWrap(w) => w, + WrapType::NoWrap => unreachable!(), + }, + tab_width: self.tab_width as usize, word_buf: Vec::new(), word_i: 0, + pos: (0, 0), } } } impl LineFormatter for ConsoleLineFormatter { - fn dimensions<'a, T>(&'a self, g_iter: T) -> (usize, usize) - where - T: Iterator>, - { + fn dimensions(&self, g_iter: RopeGraphemes) -> (usize, usize) { let mut dim: (usize, usize) = (0, 0); for (_, pos, width) in self.iter(g_iter) { @@ -81,10 +76,7 @@ impl LineFormatter for ConsoleLineFormatter { return dim; } - fn index_to_v2d<'a, T>(&'a self, g_iter: T, char_idx: usize) -> (usize, usize) - where - T: Iterator>, - { + fn index_to_v2d(&self, g_iter: RopeGraphemes, char_idx: usize) -> (usize, usize) { let mut pos = (0, 0); let mut i = 0; let mut last_width = 0; @@ -102,15 +94,12 @@ impl LineFormatter for ConsoleLineFormatter { return (pos.0, pos.1 + last_width); } - fn v2d_to_index<'a, T>( - &'a self, - g_iter: T, + fn v2d_to_index( + &self, + g_iter: RopeGraphemes, v2d: (usize, usize), _: (RoundingBehavior, RoundingBehavior), - ) -> usize - where - T: Iterator>, - { + ) -> usize { // TODO: handle rounding modes let mut prev_i = 0; let mut i = 0; @@ -131,172 +120,97 @@ impl LineFormatter for ConsoleLineFormatter { } } -// =================================================================== -// An iterator that iterates over the graphemes in a line in a -// manner consistent with the ConsoleFormatter. -// =================================================================== -pub struct ConsoleLineFormatterVisIter<'a, T> -where - T: Iterator>, -{ - grapheme_iter: T, - f: &'a ConsoleLineFormatter, - pos: (usize, usize), +//-------------------------------------------------------------------------- - indent: usize, - indent_found: bool, +/// An iterator over the visual printable characters of a piece of text, +/// yielding the text of the character, its position in 2d space, and its +/// visial width. +/// +/// TODO: handle maintaining indent, etc. +pub struct FormattingIter<'a> { + grapheme_itr: RopeGraphemes<'a>, + wrap_width: usize, + tab_width: usize, - word_buf: Vec>, + word_buf: Vec<(Cow<'a, str>, usize)>, // Printable character and its width. word_i: usize, + + pos: (usize, usize), } -impl<'a, T> ConsoleLineFormatterVisIter<'a, T> -where - T: Iterator>, -{ - fn next_nowrap(&mut self, g: RopeSlice<'a>) -> Option<(RopeSlice<'a>, (usize, usize), usize)> { - let width = grapheme_vis_width_at_vis_pos(g, self.pos.1, self.f.tab_width as usize); +impl<'a> Iterator for FormattingIter<'a> { + type Item = (Cow<'a, str>, (usize, usize), usize); + fn next(&mut self) -> Option { + // Get next word if necessary + if self.word_i >= self.word_buf.len() { + let mut word_width = 0; + self.word_buf.truncate(0); + + while let Some(g) = self.grapheme_itr.next().map(|g| Cow::::from(g)) { + let width = + grapheme_vis_width_at_vis_pos(&g, self.pos.1 + word_width, self.tab_width); + self.word_buf.push((g.clone(), width)); + word_width += width; + + if is_whitespace(&g) { + break; + } + } + + if self.word_buf.len() == 0 { + return None; + } + + // Move to next line if necessary + if (self.pos.1 + word_width) > self.wrap_width { + if self.pos.1 > 0 { + self.pos = (self.pos.0 + 1, 0); + } + } + + self.word_i = 0; + } + + // Get next grapheme and width from the current word. + let (g, g_width) = { + let (ref g, mut width) = self.word_buf[self.word_i]; + if g == "\t" { + width = grapheme_vis_width_at_vis_pos(&g, self.pos.1, self.tab_width); + } + (g, width) + }; + + // Get our character's position and update the position for the next + // grapheme. + if (self.pos.1 + g_width) > self.wrap_width && self.pos.1 > 0 { + self.pos.0 += 1; + self.pos.1 = 0; + } let pos = self.pos; - self.pos = (self.pos.0, self.pos.1 + width); - return Some((g, pos, width)); - } + self.pos.1 += g_width; - fn next_charwrap( - &mut self, - g: RopeSlice<'a>, - wrap_width: usize, - ) -> Option<(RopeSlice<'a>, (usize, usize), usize)> { - let width = grapheme_vis_width_at_vis_pos(g, self.pos.1, self.f.tab_width as usize); - - if (self.pos.1 + width) > wrap_width { - if !self.indent_found { - self.indent = 0; - self.indent_found = true; - } - - if self.f.maintain_indent { - let pos = (self.pos.0 + 1, self.indent + self.f.wrap_additional_indent); - self.pos = ( - self.pos.0 + 1, - self.indent + self.f.wrap_additional_indent + width, - ); - return Some((g, pos, width)); - } else { - let pos = (self.pos.0 + 1, self.f.wrap_additional_indent); - self.pos = (self.pos.0 + 1, self.f.wrap_additional_indent + width); - return Some((g, pos, width)); - } - } else { - if !self.indent_found { - if rope_slice_is_whitespace(&g) { - self.indent += width; - } else { - self.indent_found = true; - } - } - - let pos = self.pos; - self.pos = (self.pos.0, self.pos.1 + width); - return Some((g, pos, width)); - } + // Increment index and return. + self.word_i += 1; + return Some((g.clone(), pos, g_width)); } } -impl<'a, T> Iterator for ConsoleLineFormatterVisIter<'a, T> -where - T: Iterator>, -{ - type Item = (RopeSlice<'a>, (usize, usize), usize); - - fn next(&mut self) -> Option<(RopeSlice<'a>, (usize, usize), usize)> { - match self.f.wrap_type { - WrapType::NoWrap => { - if let Some(g) = self.grapheme_iter.next() { - return self.next_nowrap(g); - } else { - return None; - } - } - - WrapType::CharWrap(wrap_width) => { - if let Some(g) = self.grapheme_iter.next() { - return self.next_charwrap(g, wrap_width); - } else { - return None; - } - } - - WrapType::WordWrap(wrap_width) => { - // Get next word if necessary - if self.word_i >= self.word_buf.len() { - let mut word_width = 0; - self.word_buf.truncate(0); - while let Some(g) = self.grapheme_iter.next() { - self.word_buf.push(g); - let width = grapheme_vis_width_at_vis_pos( - g, - self.pos.1 + word_width, - self.f.tab_width as usize, - ); - word_width += width; - if rope_slice_is_whitespace(&g) { - break; - } - } - - if self.word_buf.len() == 0 { - return None; - } else if !self.indent_found && !rope_slice_is_whitespace(&self.word_buf[0]) { - self.indent_found = true; - } - - // Move to next line if necessary - if (self.pos.1 + word_width) > wrap_width { - if !self.indent_found { - self.indent = 0; - self.indent_found = true; - } - - if self.pos.1 > 0 { - if self.f.maintain_indent { - self.pos = - (self.pos.0 + 1, self.indent + self.f.wrap_additional_indent); - } else { - self.pos = (self.pos.0 + 1, self.f.wrap_additional_indent); - } - } - } - - self.word_i = 0; - } - - // Iterate over the word - let g = self.word_buf[self.word_i]; - self.word_i += 1; - return self.next_charwrap(g, wrap_width); - } - } - } -} - -// =================================================================== -// Helper functions -// =================================================================== - /// Returns the visual width of a grapheme given a starting /// position on a line. -fn grapheme_vis_width_at_vis_pos(g: RopeSlice, pos: usize, tab_width: usize) -> usize { +fn grapheme_vis_width_at_vis_pos(g: &str, pos: usize, tab_width: usize) -> usize { if g == "\t" { let ending_pos = ((pos / tab_width) + 1) * tab_width; return ending_pos - pos; - } else if rope_slice_is_line_ending(&g) { + } else if is_line_ending(g) { return 1; } else { return grapheme_width(&g); } } +//-------------------------------------------------------------------------- + #[cfg(test)] mod tests { #![allow(unused_imports)] diff --git a/src/term_ui/mod.rs b/src/term_ui/mod.rs index ac0744a..4d69942 100644 --- a/src/term_ui/mod.rs +++ b/src/term_ui/mod.rs @@ -14,7 +14,7 @@ use crossterm::{ use crate::{ editor::Editor, formatter::{block_count, block_index_and_range, char_range_from_block_index, LineFormatter}, - string_utils::{line_ending_to_str, rope_slice_is_line_ending, LineEnding}, + string_utils::{is_line_ending, line_ending_to_str, LineEnding}, utils::{digit_count, RopeGraphemes, Timer}, }; @@ -568,7 +568,7 @@ impl TermUI { } // Actually print the character - if rope_slice_is_line_ending(&g) { + if is_line_ending(&g) { if at_cursor { self.screen .draw(px as usize, py as usize, " ", STYLE_CURSOR); @@ -587,19 +587,9 @@ impl TermUI { } } else { if at_cursor { - self.screen.draw_rope_slice( - px as usize, - py as usize, - &g, - STYLE_CURSOR, - ); + self.screen.draw(px as usize, py as usize, &g, STYLE_CURSOR); } else { - self.screen.draw_rope_slice( - px as usize, - py as usize, - &g, - STYLE_MAIN, - ); + self.screen.draw(px as usize, py as usize, &g, STYLE_MAIN); } } } diff --git a/src/term_ui/screen.rs b/src/term_ui/screen.rs index 7409716..0b845cd 100644 --- a/src/term_ui/screen.rs +++ b/src/term_ui/screen.rs @@ -4,11 +4,9 @@ use std::io; use std::io::{BufWriter, Write}; use crossterm::{self, execute, queue}; -use ropey::RopeSlice; use unicode_segmentation::UnicodeSegmentation; -use unicode_width::UnicodeWidthStr; -use crate::utils::{grapheme_width, RopeGraphemes}; +use crate::utils::grapheme_width; use super::smallstring::SmallString; @@ -135,7 +133,7 @@ impl Screen { let mut buf = self.buf.borrow_mut(); let mut x = x; for g in UnicodeSegmentation::graphemes(text, true) { - let width = UnicodeWidthStr::width(g); + let width = grapheme_width(g); if width > 0 { if x < self.w { buf[y * self.w + x] = Some((style, g.into())); @@ -152,28 +150,6 @@ impl Screen { } } - pub(crate) fn draw_rope_slice(&self, x: usize, y: usize, text: &RopeSlice, style: Style) { - if y < self.h { - let mut buf = self.buf.borrow_mut(); - let mut x = x; - for g in RopeGraphemes::new(&text) { - let width = grapheme_width(&g); - if width > 0 { - if x < self.w { - buf[y * self.w + x] = Some((style, SmallString::from_rope_slice(&g))); - } - x += 1; - for _ in 0..(width - 1) { - if x < self.w { - buf[y * self.w + x] = None; - } - x += 1; - } - } - } - } - } - pub(crate) fn hide_cursor(&self) { let mut out = self.out.borrow_mut(); execute!(out, crossterm::cursor::Hide).unwrap(); diff --git a/src/utils.rs b/src/utils.rs index 4bf7bff..37c135e 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -16,14 +16,8 @@ pub fn digit_count(mut n: u32, b: u32) -> u32 { //============================================================= -pub fn grapheme_width(slice: &RopeSlice) -> usize { - use crate::term_ui::smallstring::SmallString; - if let Some(text) = slice.as_str() { - return UnicodeWidthStr::width(text); - } else { - let text = SmallString::from_rope_slice(slice); - return UnicodeWidthStr::width(&text[..]); - } +pub fn grapheme_width(g: &str) -> usize { + UnicodeWidthStr::width(g) } /// Finds the previous grapheme boundary before the given char position.