diff --git a/src/buffer/mod.rs b/src/buffer/mod.rs index d99f74b..7b13630 100644 --- a/src/buffer/mod.rs +++ b/src/buffer/mod.rs @@ -23,7 +23,7 @@ use self::undo_stack::{Operation::*, UndoStack}; /// A text buffer pub struct Buffer { - text: Rope, + pub text: Rope, file_path: Option, undo_stack: UndoStack, } diff --git a/src/editor/cursor.rs b/src/editor/cursor.rs index 567b3a6..3a89cb5 100644 --- a/src/editor/cursor.rs +++ b/src/editor/cursor.rs @@ -28,8 +28,8 @@ impl Cursor { } } - pub fn update_vis_start(&mut self, buf: &Buffer, f: &T) { - self.vis_start = f.index_to_horizontal_v2d(buf, self.range.0); + pub fn update_vis_start(&mut self, buf: &Buffer, f: &LineFormatter) { + self.vis_start = f.get_horizontal(buf, self.range.0); } } diff --git a/src/editor/mod.rs b/src/editor/mod.rs index a60b7ac..9ce3e00 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -11,16 +11,15 @@ use std::{ use crate::{ buffer::Buffer, formatter::LineFormatter, - formatter::RoundingBehavior::*, string_utils::{char_count, rope_slice_to_line_ending, LineEnding}, utils::{digit_count, RopeGraphemes}, }; use self::cursor::CursorSet; -pub struct Editor { +pub struct Editor { pub buffer: Buffer, - pub formatter: T, + pub formatter: LineFormatter, pub file_path: PathBuf, pub line_ending_type: LineEnding, pub soft_tabs: bool, @@ -39,9 +38,9 @@ pub struct Editor { pub cursors: CursorSet, } -impl Editor { +impl Editor { /// Create a new blank editor - pub fn new(formatter: T) -> Editor { + pub fn new(formatter: LineFormatter) -> Editor { Editor { buffer: Buffer::new(), formatter: formatter, @@ -57,7 +56,7 @@ impl Editor { } } - pub fn new_from_file(formatter: T, path: &Path) -> Editor { + pub fn new_from_file(formatter: LineFormatter, path: &Path) -> Editor { let buf = match Buffer::new_from_file(path) { Ok(b) => b, // TODO: handle un-openable file better @@ -307,28 +306,24 @@ impl Editor { // the closest cursor. // Find the first and last char index visible within the editor. - let c_first = + let c_first = self + .formatter + .set_horizontal(&self.buffer, self.view_pos.0, 0); + let mut c_last = self.formatter - .index_set_horizontal_v2d(&self.buffer, self.view_pos.0, 0, Floor); - let mut c_last = self.formatter.index_offset_vertical_v2d( - &self.buffer, - c_first, - self.view_dim.0 as isize, - (Floor, Floor), - ); - c_last = - self.formatter - .index_set_horizontal_v2d(&self.buffer, c_last, self.view_dim.1, Floor); + .offset_vertical(&self.buffer, c_first, self.view_dim.0 as isize); + c_last = self + .formatter + .set_horizontal(&self.buffer, c_last, self.view_dim.1); // Adjust the view depending on where the cursor is if self.cursors[0].range.0 < c_first { self.view_pos.0 = self.cursors[0].range.0; } else if self.cursors[0].range.0 > c_last { - self.view_pos.0 = self.formatter.index_offset_vertical_v2d( + self.view_pos.0 = self.formatter.offset_vertical( &self.buffer, self.cursors[0].range.0, -(self.view_dim.0 as isize), - (Floor, Floor), ); } } @@ -369,9 +364,7 @@ impl Editor { c.range.1 += offset; // Figure out how many spaces to insert - let vis_pos = self - .formatter - .index_to_horizontal_v2d(&self.buffer, c.range.0); + let vis_pos = self.formatter.get_horizontal(&self.buffer, c.range.0); // TODO: handle tab settings let next_tab_stop = ((vis_pos / self.soft_tab_width as usize) + 1) * self.soft_tab_width as usize; @@ -555,18 +548,12 @@ impl Editor { for c in self.cursors.iter_mut() { let vmove = -1 * n as isize; - let mut temp_index = self.formatter.index_offset_vertical_v2d( - &self.buffer, - c.range.0, - vmove, - (Round, Round), - ); - temp_index = self.formatter.index_set_horizontal_v2d( - &self.buffer, - temp_index, - c.vis_start, - Round, - ); + let mut temp_index = self + .formatter + .offset_vertical(&self.buffer, c.range.0, vmove); + temp_index = self + .formatter + .set_horizontal(&self.buffer, temp_index, c.vis_start); if !self.buffer.is_grapheme(temp_index) { temp_index = self.buffer.nth_prev_grapheme(temp_index, 1); @@ -591,18 +578,12 @@ impl Editor { for c in self.cursors.iter_mut() { let vmove = n as isize; - let mut temp_index = self.formatter.index_offset_vertical_v2d( - &self.buffer, - c.range.0, - vmove, - (Round, Round), - ); - temp_index = self.formatter.index_set_horizontal_v2d( - &self.buffer, - temp_index, - c.vis_start, - Round, - ); + let mut temp_index = self + .formatter + .offset_vertical(&self.buffer, c.range.0, vmove); + temp_index = self + .formatter + .set_horizontal(&self.buffer, temp_index, c.vis_start); if !self.buffer.is_grapheme(temp_index) { temp_index = self.buffer.nth_prev_grapheme(temp_index, 1); @@ -625,11 +606,10 @@ impl Editor { pub fn page_up(&mut self) { let move_amount = self.view_dim.0 - max(self.view_dim.0 / 8, 1); - self.view_pos.0 = self.formatter.index_offset_vertical_v2d( + self.view_pos.0 = self.formatter.offset_vertical( &self.buffer, self.view_pos.0, -1 * move_amount as isize, - (Round, Round), ); self.cursor_up(move_amount); @@ -640,12 +620,9 @@ impl Editor { pub fn page_down(&mut self) { let move_amount = self.view_dim.0 - max(self.view_dim.0 / 8, 1); - self.view_pos.0 = self.formatter.index_offset_vertical_v2d( - &self.buffer, - self.view_pos.0, - move_amount as isize, - (Round, Round), - ); + self.view_pos.0 = + self.formatter + .offset_vertical(&self.buffer, self.view_pos.0, move_amount as isize); self.cursor_down(move_amount); @@ -656,12 +633,9 @@ impl Editor { pub fn jump_to_line(&mut self, n: usize) { let pos = self.buffer.line_col_to_index((n, 0)); self.cursors.truncate(1); - self.cursors[0].range.0 = self.formatter.index_set_horizontal_v2d( - &self.buffer, - pos, - self.cursors[0].vis_start, - Round, - ); + self.cursors[0].range.0 = + self.formatter + .set_horizontal(&self.buffer, pos, self.cursors[0].vis_start); self.cursors[0].range.1 = self.cursors[0].range.0; // Adjust view diff --git a/src/formatter.rs b/src/formatter.rs index 077deb9..9774f4f 100644 --- a/src/formatter.rs +++ b/src/formatter.rs @@ -1,10 +1,12 @@ -use std::cmp::min; +use std::borrow::Cow; -use ropey::RopeSlice; +use ropey::{Rope, RopeSlice}; use crate::{ buffer::Buffer, - utils::{is_grapheme_boundary, prev_grapheme_boundary, RopeGraphemes}, + string_utils::char_count, + string_utils::{is_line_ending, str_is_whitespace}, + utils::{grapheme_width, is_grapheme_boundary, prev_grapheme_boundary, RopeGraphemes}, }; // Maximum chars in a line before a soft line break is forced. @@ -17,176 +19,457 @@ const LINE_BLOCK_LENGTH: usize = 1 << 12; // breaks. const LINE_BLOCK_FUDGE: usize = 32; -#[allow(dead_code)] -#[derive(Copy, Clone, PartialEq)] -pub enum RoundingBehavior { - Round, - Floor, - Ceiling, +//-------------------------------------------------------------------------- + +#[derive(Clone)] +pub struct LineFormatter { + pub tab_width: usize, + pub wrap_width: usize, + pub maintain_indent: bool, + pub wrap_extra_indent: usize, } -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(&self, g_iter: RopeGraphemes) -> (usize, usize); +impl LineFormatter { + pub fn new(tab_width: usize) -> LineFormatter { + LineFormatter { + tab_width: tab_width, + wrap_width: 80, + maintain_indent: true, + wrap_extra_indent: 2, + } + } - /// 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(&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( - &self, - g_iter: RopeGraphemes, - v2d: (usize, usize), - rounding: (RoundingBehavior, RoundingBehavior), - ) -> usize; - - /// Converts from char index to the horizontal 2d char index. - fn index_to_horizontal_v2d(&self, buf: &Buffer, char_idx: usize) -> usize { + /// Returns an iterator over the blocks of the buffer, starting at the + /// block containing the given char. Also returns the offset of that char + /// relative to the start of the first block. + pub fn iter<'b>(&'b self, buf: &'b Buffer, char_idx: usize) -> (Blocks<'b>, usize) { + // Get the line. let (line_i, col_i) = buf.index_to_line_col(char_idx); let line = buf.get_line(line_i); // Find the right block in the line, and the index within that block - let (_, block_range) = block_index_and_range(&line, col_i); + let (block_index, block_range) = block_index_and_range(&line, col_i); let col_i_adjusted = col_i - block_range.0; - // Get an iter into the right block - let g_iter = RopeGraphemes::new(&line.slice(block_range.0..block_range.1)); - return self.index_to_v2d(g_iter, col_i_adjusted).1; + ( + Blocks { + formatter: self, + buf: &buf.text, + line_idx: line_i, + line_block_count: block_count(&line), + block_idx: block_index, + }, + col_i_adjusted, + ) } - /// Takes a char index and a visual vertical offset, and returns the char - /// index after that visual offset is applied. - fn index_offset_vertical_v2d( - &self, - buf: &Buffer, - char_idx: usize, - offset: isize, - rounding: (RoundingBehavior, RoundingBehavior), - ) -> usize { - // TODO: handle rounding modes - // TODO: do this with bidirectional line iterator + /// Converts from char index to the horizontal 2d char index. + pub fn get_horizontal(&self, buf: &Buffer, char_idx: usize) -> usize { + let (_, vis_iter, char_offset) = self.block_vis_iter_and_char_offset(buf, char_idx); - // Get the line and char index within that line. - let (mut line_i, mut col_i) = buf.index_to_line_col(char_idx); - let mut line = buf.get_line(line_i); + // Traverse the iterator and find the horizontal position of the char + // index. + let mut hpos = 0; + let mut i = 0; + let mut last_width = 0; - // Get the block information for the char offset in the line. - let (line_block, block_range) = block_index_and_range(&line, col_i); - let col_i_adjusted = col_i - block_range.0; + for (g, pos, width) in vis_iter { + hpos = pos.1; + last_width = width; + i += char_count(&g); - // Get the 2d coordinates within the block. - let (mut y, x) = self.index_to_v2d( - RopeGraphemes::new(&line.slice(block_range.0..block_range.1)), - col_i_adjusted, - ); - - // First, find the right line while keeping track of the vertical offset - let mut new_y = y as isize + offset; - - let mut block_index: usize = line_block; - loop { - line = buf.get_line(line_i); - let (block_start, block_end) = char_range_from_block_index(&line, block_index); - let (h, _) = self.dimensions(RopeGraphemes::new(&line.slice(block_start..block_end))); - - if new_y >= 0 && new_y < h as isize { - y = new_y as usize; - break; - } else { - if new_y > 0 { - let is_last_block = block_index >= (block_count(&line) - 1); - - // Check for off-the-end - if is_last_block && (line_i + 1) >= buf.line_count() { - return buf.char_count(); - } - - if is_last_block { - line_i += 1; - block_index = 0; - } else { - block_index += 1; - } - new_y -= h as isize; - } else if new_y < 0 { - // Check for off-the-end - if block_index == 0 && line_i == 0 { - return 0; - } - - if block_index == 0 { - line_i -= 1; - line = buf.get_line(line_i); - block_index = block_count(&line) - 1; - } else { - block_index -= 1; - } - let (block_start, block_end) = char_range_from_block_index(&line, block_index); - let (h, _) = - self.dimensions(RopeGraphemes::new(&line.slice(block_start..block_end))); - new_y += h as isize; - } else { - unreachable!(); - } + if i > char_offset { + return hpos; } } - // Next, convert the resulting coordinates back into buffer-wide - // coordinates. - let (block_start, block_end) = char_range_from_block_index(&line, block_index); - let block_len = block_end - block_start; - let block_slice = line.slice(block_start..block_end); - let block_col_i = min( - self.v2d_to_index(RopeGraphemes::new(&block_slice), (y, x), rounding), - block_len.saturating_sub(1), - ); - col_i = block_start + block_col_i; - - return buf.line_col_to_index((line_i, col_i)); + // If we went off the end, calculate the position of the end of the + // block. + 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, - /// but offset to have the desired horizontal position. - fn index_set_horizontal_v2d( + /// but offset to have the desired horizontal position (or as close as is + /// possible. + pub fn set_horizontal(&self, buf: &Buffer, char_idx: usize, horizontal: usize) -> usize { + let (_, vis_iter, char_offset) = self.block_vis_iter_and_char_offset(buf, char_idx); + + let mut hpos_char_idx = None; + let mut i = 0; + let mut last_i = 0; + let mut last_pos = (0, 0); + for (g, pos, width) in vis_iter { + // Check if we moved to the next line. + if pos.0 > last_pos.0 { + // If we did, but we're already passed the given char_idx, + // 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 last_i; + } + + // Otherwise reset and keep going. + hpos_char_idx = None; + } + + // Check if we found the horizontal position on this line, + // and set it if so. + if hpos_char_idx == None && horizontal < (pos.1 + width) { + hpos_char_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_offset && hpos_char_idx != None { + return hpos_char_idx.unwrap(); + } + + last_pos = pos; + last_i = i; + i += char_count(&g); + } + + // If we reached the end of the text, return the last char index. + return i; + } + + /// Takes a char index and a visual vertical offset, and returns the char + /// index after that visual offset is applied. + pub fn offset_vertical(&self, buf: &Buffer, char_idx: usize, v_offset: isize) -> usize { + let mut char_idx = char_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 the vertical size of the block and the vertical + // position of the char_idx within it. + let block_v_dim = (block_vis_iter.clone().last().unwrap().1).0 + 1; + let char_v_pos = block_vis_iter.clone().vpos(char_offset); + + // Get the char's vertical position within the block after offset + // by v_offset. + let offset_char_v_pos = char_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 we're off the start of the block. + if char_idx == 0 { + // We reached the start of the whole buffer. + break; + } else { + // Set our variables appropriately for the next iteration. + char_idx -= char_offset + 1; + v_offset += char_v_pos as isize + 1; + } + } else if offset_char_v_pos >= block_v_dim as isize { + // If we're off the end of the block. + if char_idx >= buf.text.len_chars() { + // We reached the end of the whole buffer. + char_idx = buf.text.len_chars(); + break; + } else { + // Set our variables appropriately for the next iteration. + char_idx += block.len_chars() - char_offset; + v_offset -= block_v_dim as isize - char_v_pos as isize; + } + } else { + // If the vertical offset is within this block, calculate an + // 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 { + break; + } + i += char_count(&g); + } + char_idx += block.len_chars() - char_offset + i; + v_offset = 0; + } + } + + return char_idx; + } + + //---------------------------------------------------- + // Helper methods + + /// Returns the amount of indentation to use for soft-line wrapping + /// given the start of a line. + fn get_line_indent(&self, line: &RopeSlice) -> usize { + if !self.maintain_indent { + return 0; + } + + let mut indent = 0; + for c in line.chars() { + match c { + ' ' => { + indent += 1; + } + '\t' => { + indent = tab_stop_from_vis_pos(indent, self.tab_width); + } + _ => break, + } + + // If the indent is too long for the wrap width, do no indentation. + if (indent + self.wrap_extra_indent + 2) > self.wrap_width { + return 0; + } + } + + 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>( &self, - buf: &Buffer, + buf: &'b Buffer, char_idx: usize, - horizontal: usize, - rounding: RoundingBehavior, - ) -> usize { - // Get the line info. + ) -> (RopeSlice<'b>, BlockVisIter<'b>, usize) { let (line_i, col_i) = buf.index_to_line_col(char_idx); let line = buf.get_line(line_i); - // Get the right block within the line. - let (block_i, block_range) = block_index_and_range(&line, col_i); + // 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 col_i_adjusted = col_i - block_range.0; - // Calculate the horizontal position. - let (v, _) = self.index_to_v2d( - RopeGraphemes::new(&line.slice(block_range.0..block_range.1)), - col_i_adjusted, - ); - let block_col_i = self.v2d_to_index( - RopeGraphemes::new(&line.slice(block_range.0..block_range.1)), - (v, horizontal), - (RoundingBehavior::Floor, rounding), - ); - let new_col_i = if (line_i + 1) < buf.line_count() || (block_i + 1) < block_count(&line) { - min(block_range.0 + block_col_i, block_range.1.saturating_sub(1)) - } else { - min(block_range.0 + block_col_i, block_range.1) - }; + // Get the right block and an iter into it. + let block = line.slice(block_range.0..block_range.1); + let g_iter = RopeGraphemes::new(&block); - return (char_idx + new_col_i) - col_i; + // Get an appropriate visual block iter. + let vis_iter = BlockVisIter::new( + g_iter, + self.wrap_width, + self.tab_width, + block_index == 0, + if block_index == 0 { + 0 + } else { + self.get_line_indent(&line) + }, + self.wrap_extra_indent, + ); + + (block, vis_iter, col_i_adjusted) } } +//-------------------------------------------------------------------------- + +#[derive(Clone)] +pub struct Blocks<'a> { + formatter: &'a LineFormatter, + buf: &'a Rope, + line_idx: usize, + line_block_count: usize, + block_idx: usize, +} + +impl<'a> Iterator for Blocks<'a> { + type Item = (BlockVisIter<'a>, bool); + + fn next(&mut self) -> Option { + // Check if we're done already. + if self.line_idx >= self.buf.len_lines() { + 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 block = line.slice(start..end); + let iter = BlockVisIter::new( + RopeGraphemes::new(&block), + self.formatter.wrap_width, + self.formatter.tab_width, + self.block_idx == 0, + if self.block_idx == 0 { + 0 + } else { + self.formatter.get_line_indent(&line) + }, + self.formatter.wrap_extra_indent, + ); + + (iter, self.block_idx == 0) + }; + + // Progress the values of the iterator. + self.block_idx += 1; + 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)); + } + } + + // Return. + Some((iter, is_line_start)) + } +} + +//-------------------------------------------------------------------------- + +/// An iterator over the visual printable characters of a block of text, +/// yielding the text of the character, its position in 2d space, and its +/// visial width. +#[derive(Clone)] +pub struct BlockVisIter<'a> { + grapheme_itr: RopeGraphemes<'a>, + + wrap_width: usize, + tab_width: usize, + indent: usize, // Size of soft indent to use. + wrap_extra_indent: usize, // Additional amount to indent soft-wrapped lines. + finding_indent: bool, + + word_buf: Vec<(Cow<'a, str>, usize)>, // Printable character and its width. + word_i: usize, + pos: (usize, usize), +} + +impl<'a> BlockVisIter<'a> { + fn new( + grapheme_itr: RopeGraphemes<'a>, + wrap_width: usize, + tab_width: usize, + find_indent: bool, + starting_indent: usize, + wrap_extra_indent: usize, + ) -> BlockVisIter<'a> { + BlockVisIter { + grapheme_itr: grapheme_itr, + wrap_width: wrap_width, + tab_width: tab_width, + indent: starting_indent, + wrap_extra_indent: wrap_extra_indent, + finding_indent: find_indent, + + word_buf: Vec::new(), + word_i: 0, + pos: (0, 0), + } + } + + pub fn vpos(&mut self, char_offset: usize) -> usize { + let mut vpos = 0; + let mut i = 0; + + for (g, pos, _) in self { + vpos = pos.0; + i += char_count(&g); + + if i > char_offset { + break; + } + } + + vpos + } +} + +impl<'a> Iterator for BlockVisIter<'a> { + type Item = (Cow<'a, str>, (usize, usize), usize); + + fn next(&mut self) -> Option { + if self.pos == (0, 0) { + self.pos = (0, self.indent); + } + + // 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 str_is_whitespace(&g) { + if self.finding_indent && (g.as_bytes()[0] == 0x09 || g.as_bytes()[0] == 0x20) { + if (self.indent + self.wrap_extra_indent + width + 2) > self.wrap_width { + // Cancel indentation if it's too long for the screen. + self.indent = 0; + self.wrap_extra_indent = 0; + self.finding_indent = false; + } else { + self.indent += width; + } + } + break; + } else { + self.finding_indent = false; + } + } + + if self.word_buf.len() == 0 { + return None; + } + + // Move to next line if necessary + if (self.pos.1 + word_width) > self.wrap_width && (self.pos.1 > self.indent) { + if self.pos.1 > 0 { + self.pos = (self.pos.0 + 1, self.indent + self.wrap_extra_indent); + } + } + + 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 = self.indent + self.wrap_extra_indent; + } + let pos = self.pos; + self.pos.1 += g_width; + + // Increment index and return. + self.word_i += 1; + return Some((g.clone(), pos, g_width)); + } +} + +/// Returns the visual width of a grapheme given a starting +/// position on a line. +fn grapheme_vis_width_at_vis_pos(g: &str, pos: usize, tab_width: usize) -> usize { + if g == "\t" { + return tab_stop_from_vis_pos(pos, tab_width) - pos; + } else if is_line_ending(g) { + return 1; + } else { + return grapheme_width(&g); + } +} + +fn tab_stop_from_vis_pos(pos: usize, tab_width: usize) -> usize { + ((pos / tab_width) + 1) * tab_width +} + +//-------------------------------------------------------------------------- + // Finds the best break at or before the given char index, bounded by // the given `lower_limit`. pub fn find_good_break(slice: &RopeSlice, lower_limit: usize, char_idx: usize) -> usize { diff --git a/src/main.rs b/src/main.rs index 30d52a6..07b71cb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,7 @@ use std::{io::Write, path::Path}; use clap::{App, Arg}; use editor::Editor; -use term_ui::formatter::ConsoleLineFormatter; +use formatter::LineFormatter; use term_ui::TermUI; mod buffer; @@ -29,9 +29,9 @@ fn main() { // Load file, if specified let editor = if let Some(filepath) = args.value_of("file") { - Editor::new_from_file(ConsoleLineFormatter::new(4), &Path::new(&filepath[..])) + Editor::new_from_file(LineFormatter::new(4), &Path::new(&filepath[..])) } else { - Editor::new(ConsoleLineFormatter::new(4)) + Editor::new(LineFormatter::new(4)) }; // Holds stderr output in an internal buffer, and prints it when dropped. diff --git a/src/string_utils.rs b/src/string_utils.rs index 8626bff..ff79c3b 100644 --- a/src/string_utils.rs +++ b/src/string_utils.rs @@ -10,28 +10,36 @@ pub fn is_line_ending(text: &str) -> bool { } } -pub fn is_whitespace(text: &str) -> bool { +pub fn str_is_whitespace(text: &str) -> bool { + if let Some(c) = text.chars().nth(0) { + is_whitespace(c) + } else { + false + } +} + +pub fn is_whitespace(c: char) -> bool { // TODO: this is a naive categorization of whitespace characters. // For better categorization these should be split up into groups // based on e.g. breaking vs non-breaking spaces, among other things. - match text.chars().nth(0) { - //Some('\u{1680}') | // OGHAM SPACE MARK (here for completeness, but usually displayed as a dash, not as whitespace) - Some('\u{0009}') | // CHARACTER TABULATION - Some('\u{0020}') | // SPACE - Some('\u{00A0}') | // NO-BREAK SPACE - Some('\u{180E}') | // MONGOLIAN VOWEL SEPARATOR - Some('\u{202F}') | // NARROW NO-BREAK SPACE - Some('\u{205F}') | // MEDIUM MATHEMATICAL SPACE - Some('\u{3000}') | // IDEOGRAPHIC SPACE - Some('\u{FEFF}') // ZERO WIDTH NO-BREAK SPACE + match c { + //'\u{1680}' | // OGHAM SPACE MARK (here for completeness, but usually displayed as a dash, not as whitespace) + '\u{0009}' | // CHARACTER TABULATION + '\u{0020}' | // SPACE + '\u{00A0}' | // NO-BREAK SPACE + '\u{180E}' | // MONGOLIAN VOWEL SEPARATOR + '\u{202F}' | // NARROW NO-BREAK SPACE + '\u{205F}' | // MEDIUM MATHEMATICAL SPACE + '\u{3000}' | // IDEOGRAPHIC SPACE + '\u{FEFF}' // ZERO WIDTH NO-BREAK SPACE => true, // EN QUAD, EM QUAD, EN SPACE, EM SPACE, THREE-PER-EM SPACE, // FOUR-PER-EM SPACE, SIX-PER-EM SPACE, FIGURE SPACE, // PUNCTUATION SPACE, THIN SPACE, HAIR SPACE, ZERO WIDTH SPACE. - Some(c) if c >= '\u{2000}' && c <= '\u{200B}' => true, + c if c >= '\u{2000}' && c <= '\u{200B}' => true, - // None, or not a matching whitespace character. + // Not a matching whitespace character. _ => false, } } @@ -108,9 +116,11 @@ mod tests { #[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/formatter.rs b/src/term_ui/formatter.rs index 4c01d74..c653210 100644 --- a/src/term_ui/formatter.rs +++ b/src/term_ui/formatter.rs @@ -1,720 +1,512 @@ -use std::{borrow::Cow, cmp::max}; - -use crate::{ - formatter::{LineFormatter, RoundingBehavior}, - string_utils::{is_line_ending, is_whitespace}, - utils::{grapheme_width, RopeGraphemes}, -}; - -// =================================================================== -// LineFormatter implementation for terminals/consoles. -// =================================================================== - -pub struct ConsoleLineFormatter { - pub tab_width: u8, - pub wrap_width: usize, - pub maintain_indent: bool, - pub wrap_additional_indent: usize, -} - -impl ConsoleLineFormatter { - pub fn new(tab_width: u8) -> ConsoleLineFormatter { - ConsoleLineFormatter { - tab_width: tab_width, - wrap_width: 40, - maintain_indent: true, - wrap_additional_indent: 0, - } - } - - pub fn set_wrap_width(&mut self, width: usize) { - self.wrap_width = width; - } - - pub fn iter<'a>(&self, g_iter: RopeGraphemes<'a>) -> FormattingIter<'a> { - FormattingIter { - grapheme_itr: g_iter, - wrap_width: self.wrap_width, - tab_width: self.tab_width as usize, - word_buf: Vec::new(), - word_i: 0, - indent: 0, - extra_indent: 2, - finding_indent: true, - pos: (0, 0), - } - } -} - -impl LineFormatter for ConsoleLineFormatter { - fn dimensions(&self, g_iter: RopeGraphemes) -> (usize, usize) { - let mut dim: (usize, usize) = (0, 0); - - for (_, pos, width) in self.iter(g_iter) { - dim = (max(dim.0, pos.0), max(dim.1, pos.1 + width)); - } - - dim.0 += 1; - - return dim; - } - - 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; - - for (g, _pos, width) in self.iter(g_iter) { - pos = _pos; - last_width = width; - i += g.chars().count(); - - if i > char_idx { - return pos; - } - } - - return (pos.0, pos.1 + last_width); - } - - fn v2d_to_index( - &self, - g_iter: RopeGraphemes, - v2d: (usize, usize), - _: (RoundingBehavior, RoundingBehavior), - ) -> usize { - // TODO: handle rounding modes - let mut prev_i = 0; - let mut i = 0; - - for (g, pos, _) in self.iter(g_iter) { - if pos.0 > v2d.0 { - i = prev_i; - break; - } else if pos.0 == v2d.0 && pos.1 >= v2d.1 { - break; - } - - prev_i = i; - i += g.chars().count(); - } - - return i; - } -} - -//-------------------------------------------------------------------------- - -/// 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<(Cow<'a, str>, usize)>, // Printable character and its width. - word_i: usize, - - indent: usize, // Size of soft indent to use. - extra_indent: usize, // Additional amount to indent soft-wrapped lines. - finding_indent: bool, - - pos: (usize, usize), -} - -impl<'a> Iterator for FormattingIter<'a> { - type Item = (Cow<'a, str>, (usize, usize), usize); - - fn next(&mut self) -> Option { - if self.pos == (0, 0) { - self.pos = (0, self.indent); - } - - // 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) { - if self.finding_indent { - if (self.indent + self.extra_indent + width + 2) > self.wrap_width { - // Cancel indentation if it's too long for the screen. - self.indent = 0; - self.extra_indent = 0; - self.finding_indent = false; - } else { - self.indent += width; - } - } - break; - } else { - self.finding_indent = false; - } - } - - if self.word_buf.len() == 0 { - return None; - } - - // Move to next line if necessary - if (self.pos.1 + word_width) > self.wrap_width && (self.pos.1 > self.indent) { - if self.pos.1 > 0 { - self.pos = (self.pos.0 + 1, self.indent + self.extra_indent); - } - } - - 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 = self.indent + self.extra_indent; - } - let pos = self.pos; - self.pos.1 += g_width; - - // Increment index and return. - self.word_i += 1; - return Some((g.clone(), pos, g_width)); - } -} - -/// Returns the visual width of a grapheme given a starting -/// position on a line. -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 is_line_ending(g) { - return 1; - } else { - return grapheme_width(&g); - } -} - -//-------------------------------------------------------------------------- - -#[cfg(test)] -mod tests { - #![allow(unused_imports)] - use ropey::Rope; - - use crate::buffer::Buffer; - use crate::formatter::LineFormatter; - use crate::formatter::RoundingBehavior::{Ceiling, Floor, Round}; - use crate::utils::RopeGraphemes; - - use super::*; - - #[test] - fn dimensions_1() { - let text = Rope::from_str("Hello there, stranger!"); // 22 graphemes long - - let mut f = ConsoleLineFormatter::new(4); - f.wrap_width = 80; - f.maintain_indent = false; - f.wrap_additional_indent = 0; - - assert_eq!(f.dimensions(RopeGraphemes::new(&text.slice(..))), (1, 22)); - } - - #[test] - fn dimensions_3() { - let text = Rope::from_str("Hello there, stranger! How are you doing this fine day?"); // 56 graphemes long - - let mut f = ConsoleLineFormatter::new(4); - f.wrap_width = 12; - f.maintain_indent = false; - f.wrap_additional_indent = 0; - - assert_eq!(f.dimensions(RopeGraphemes::new(&text.slice(..))), (6, 12)); - } - - #[test] - fn dimensions_4() { - // 55 graphemes long - let text = Rope::from_str( - "税マイミ文末\ - レ日題イぽじ\ - や男目統ス公\ - 身みトしつ結\ - 煮ヱマレ断西\ - ロ領視りいぽ\ - 凱字テ式重反\ - てす献罪がご\ - く官俵呉嫁ー\ - 。", - ); - - let mut f = ConsoleLineFormatter::new(4); - f.wrap_width = 12; - f.maintain_indent = false; - f.wrap_additional_indent = 0; - - assert_eq!(f.dimensions(RopeGraphemes::new(&text.slice(..))), (10, 12)); - } - - #[test] - fn dimensions_5() { - // 55 graphemes long - let text = Rope::from_str( - "税マイミ文末\ - レ日題イぽじ\ - や男目統ス公\ - 身みトしつ結\ - 煮ヱマレ断西\ - ロ領視りいぽ\ - 凱字テ式重反\ - てす献罪がご\ - く官俵呉嫁ー\ - 。", - ); - - let mut f = ConsoleLineFormatter::new(4); - f.wrap_width = 12; - f.maintain_indent = false; - f.wrap_additional_indent = 0; - - assert_eq!(f.dimensions(RopeGraphemes::new(&text.slice(..))), (10, 12)); - } - - #[test] - fn index_to_v2d_1() { - let text = Rope::from_str("Hello there, stranger!"); // 22 graphemes long - - let mut f = ConsoleLineFormatter::new(4); - f.wrap_width = 80; - f.maintain_indent = false; - f.wrap_additional_indent = 0; - - assert_eq!( - f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 0), - (0, 0) - ); - assert_eq!( - f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 5), - (0, 5) - ); - assert_eq!( - f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 22), - (0, 22) - ); - assert_eq!( - f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 23), - (0, 22) - ); - } - - #[test] - fn index_to_v2d_2() { - let text = Rope::from_str("Hello there, stranger! How are you doing this fine day?"); // 56 graphemes long - - let mut f = ConsoleLineFormatter::new(4); - f.wrap_width = 12; // Was char wrap. - f.maintain_indent = false; - f.wrap_additional_indent = 0; - - assert_eq!( - f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 0), - (0, 0) - ); - assert_eq!( - f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 5), - (0, 5) - ); - assert_eq!( - f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 11), - (0, 11) - ); - - assert_eq!( - f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 12), - (1, 0) - ); - assert_eq!( - f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 15), - (1, 3) - ); - assert_eq!( - f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 23), - (1, 11) - ); - - assert_eq!( - f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 24), - (2, 0) - ); - assert_eq!( - f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 28), - (2, 4) - ); - assert_eq!( - f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 35), - (2, 11) - ); - - assert_eq!( - f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 36), - (3, 0) - ); - assert_eq!( - f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 43), - (3, 7) - ); - assert_eq!( - f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 47), - (3, 11) - ); - - assert_eq!( - f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 48), - (4, 0) - ); - assert_eq!( - f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 50), - (4, 2) - ); - assert_eq!( - f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 56), - (4, 8) - ); - - assert_eq!( - f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 57), - (4, 8) - ); - } - - #[test] - fn v2d_to_index_1() { - let text = Rope::from_str("Hello there, stranger!"); // 22 graphemes long - - let mut f = ConsoleLineFormatter::new(4); - f.wrap_width = 80; - f.maintain_indent = false; - f.wrap_additional_indent = 0; - - assert_eq!( - f.v2d_to_index(RopeGraphemes::new(&text.slice(..)), (0, 0), (Floor, Floor)), - 0 - ); - assert_eq!( - f.v2d_to_index(RopeGraphemes::new(&text.slice(..)), (0, 5), (Floor, Floor)), - 5 - ); - assert_eq!( - f.v2d_to_index(RopeGraphemes::new(&text.slice(..)), (0, 22), (Floor, Floor)), - 22 - ); - assert_eq!( - f.v2d_to_index(RopeGraphemes::new(&text.slice(..)), (0, 23), (Floor, Floor)), - 22 - ); - assert_eq!( - f.v2d_to_index(RopeGraphemes::new(&text.slice(..)), (1, 0), (Floor, Floor)), - 22 - ); - assert_eq!( - f.v2d_to_index(RopeGraphemes::new(&text.slice(..)), (1, 1), (Floor, Floor)), - 22 - ); - } - - #[test] - fn v2d_to_index_2() { - let text = Rope::from_str("Hello there, stranger! How are you doing this fine day?"); // 56 graphemes long - - let mut f = ConsoleLineFormatter::new(4); - f.wrap_width = 12; // Was char wrap. - f.maintain_indent = false; - f.wrap_additional_indent = 0; - - assert_eq!( - f.v2d_to_index(RopeGraphemes::new(&text.slice(..)), (0, 0), (Floor, Floor)), - 0 - ); - assert_eq!( - f.v2d_to_index(RopeGraphemes::new(&text.slice(..)), (0, 11), (Floor, Floor)), - 11 - ); - assert_eq!( - f.v2d_to_index(RopeGraphemes::new(&text.slice(..)), (0, 12), (Floor, Floor)), - 11 - ); - - assert_eq!( - f.v2d_to_index(RopeGraphemes::new(&text.slice(..)), (1, 0), (Floor, Floor)), - 12 - ); - assert_eq!( - f.v2d_to_index(RopeGraphemes::new(&text.slice(..)), (1, 11), (Floor, Floor)), - 23 - ); - assert_eq!( - f.v2d_to_index(RopeGraphemes::new(&text.slice(..)), (1, 12), (Floor, Floor)), - 23 - ); - - assert_eq!( - f.v2d_to_index(RopeGraphemes::new(&text.slice(..)), (2, 0), (Floor, Floor)), - 24 - ); - assert_eq!( - f.v2d_to_index(RopeGraphemes::new(&text.slice(..)), (2, 11), (Floor, Floor)), - 35 - ); - assert_eq!( - f.v2d_to_index(RopeGraphemes::new(&text.slice(..)), (2, 12), (Floor, Floor)), - 35 - ); - - assert_eq!( - f.v2d_to_index(RopeGraphemes::new(&text.slice(..)), (3, 0), (Floor, Floor)), - 36 - ); - assert_eq!( - f.v2d_to_index(RopeGraphemes::new(&text.slice(..)), (3, 11), (Floor, Floor)), - 47 - ); - assert_eq!( - f.v2d_to_index(RopeGraphemes::new(&text.slice(..)), (3, 12), (Floor, Floor)), - 47 - ); - - assert_eq!( - f.v2d_to_index(RopeGraphemes::new(&text.slice(..)), (4, 0), (Floor, Floor)), - 48 - ); - assert_eq!( - f.v2d_to_index(RopeGraphemes::new(&text.slice(..)), (4, 7), (Floor, Floor)), - 55 - ); - assert_eq!( - f.v2d_to_index(RopeGraphemes::new(&text.slice(..)), (4, 8), (Floor, Floor)), - 56 - ); - assert_eq!( - f.v2d_to_index(RopeGraphemes::new(&text.slice(..)), (4, 9), (Floor, Floor)), - 56 - ); - } - - #[test] - fn index_to_horizontal_v2d_1() { - let b = Buffer::new_from_str("Hello there, stranger!\nHow are you doing this fine day?"); // 55 graphemes long - - let mut f = ConsoleLineFormatter::new(4); - f.wrap_width = 80; - f.maintain_indent = false; - f.wrap_additional_indent = 0; - - assert_eq!(f.index_to_horizontal_v2d(&b, 0), 0); - assert_eq!(f.index_to_horizontal_v2d(&b, 5), 5); - assert_eq!(f.index_to_horizontal_v2d(&b, 26), 3); - assert_eq!(f.index_to_horizontal_v2d(&b, 55), 32); - assert_eq!(f.index_to_horizontal_v2d(&b, 56), 32); - } - - #[test] - fn index_to_horizontal_v2d_2() { - let b = Buffer::new_from_str("Hello there, stranger!\nHow are you doing this fine day?"); // 55 graphemes long - - let mut f = ConsoleLineFormatter::new(4); - f.wrap_width = 12; // Was char wrap. - f.maintain_indent = false; - f.wrap_additional_indent = 0; - - assert_eq!(f.index_to_horizontal_v2d(&b, 0), 0); - assert_eq!(f.index_to_horizontal_v2d(&b, 11), 11); - - assert_eq!(f.index_to_horizontal_v2d(&b, 12), 0); - assert_eq!(f.index_to_horizontal_v2d(&b, 22), 10); - - assert_eq!(f.index_to_horizontal_v2d(&b, 23), 0); - assert_eq!(f.index_to_horizontal_v2d(&b, 34), 11); - - assert_eq!(f.index_to_horizontal_v2d(&b, 35), 0); - assert_eq!(f.index_to_horizontal_v2d(&b, 46), 11); - - assert_eq!(f.index_to_horizontal_v2d(&b, 47), 0); - assert_eq!(f.index_to_horizontal_v2d(&b, 55), 8); - assert_eq!(f.index_to_horizontal_v2d(&b, 56), 8); - } - - #[test] - fn index_to_horizontal_v2d_3() { - let b = Buffer::new_from_str("Hello there, stranger!\nHow are you doing this fine day?"); // 55 graphemes long - - let mut f = ConsoleLineFormatter::new(4); - f.wrap_width = 12; - f.maintain_indent = false; - f.wrap_additional_indent = 0; - - assert_eq!(f.index_to_horizontal_v2d(&b, 0), 0); - assert_eq!(f.index_to_horizontal_v2d(&b, 5), 5); - - assert_eq!(f.index_to_horizontal_v2d(&b, 6), 0); - assert_eq!(f.index_to_horizontal_v2d(&b, 12), 6); - - assert_eq!(f.index_to_horizontal_v2d(&b, 13), 0); - assert_eq!(f.index_to_horizontal_v2d(&b, 22), 9); - - assert_eq!(f.index_to_horizontal_v2d(&b, 23), 0); - assert_eq!(f.index_to_horizontal_v2d(&b, 34), 11); - - assert_eq!(f.index_to_horizontal_v2d(&b, 35), 0); - assert_eq!(f.index_to_horizontal_v2d(&b, 45), 10); - - assert_eq!(f.index_to_horizontal_v2d(&b, 46), 0); - assert_eq!(f.index_to_horizontal_v2d(&b, 55), 9); - assert_eq!(f.index_to_horizontal_v2d(&b, 56), 9); - } - - #[test] - fn index_set_horizontal_v2d_1() { - let b = Buffer::new_from_str("Hello there, stranger!\nHow are you doing this fine day?"); // 55 graphemes long - - let mut f = ConsoleLineFormatter::new(4); - f.wrap_width = 80; - f.maintain_indent = false; - f.wrap_additional_indent = 0; - - assert_eq!(f.index_set_horizontal_v2d(&b, 0, 0, Floor), 0); - assert_eq!(f.index_set_horizontal_v2d(&b, 0, 22, Floor), 22); - assert_eq!(f.index_set_horizontal_v2d(&b, 0, 23, Floor), 22); - - assert_eq!(f.index_set_horizontal_v2d(&b, 8, 0, Floor), 0); - assert_eq!(f.index_set_horizontal_v2d(&b, 8, 22, Floor), 22); - assert_eq!(f.index_set_horizontal_v2d(&b, 8, 23, Floor), 22); - - assert_eq!(f.index_set_horizontal_v2d(&b, 22, 0, Floor), 0); - assert_eq!(f.index_set_horizontal_v2d(&b, 22, 22, Floor), 22); - assert_eq!(f.index_set_horizontal_v2d(&b, 22, 23, Floor), 22); - - assert_eq!(f.index_set_horizontal_v2d(&b, 23, 0, Floor), 23); - assert_eq!(f.index_set_horizontal_v2d(&b, 23, 32, Floor), 55); - assert_eq!(f.index_set_horizontal_v2d(&b, 23, 33, Floor), 55); - - assert_eq!(f.index_set_horizontal_v2d(&b, 28, 0, Floor), 23); - assert_eq!(f.index_set_horizontal_v2d(&b, 28, 32, Floor), 55); - assert_eq!(f.index_set_horizontal_v2d(&b, 28, 33, Floor), 55); - - assert_eq!(f.index_set_horizontal_v2d(&b, 55, 0, Floor), 23); - assert_eq!(f.index_set_horizontal_v2d(&b, 55, 32, Floor), 55); - assert_eq!(f.index_set_horizontal_v2d(&b, 55, 33, Floor), 55); - } - - #[test] - fn index_set_horizontal_v2d_2() { - let b = Buffer::new_from_str("Hello there, stranger! How are you doing this fine day?"); // 55 graphemes long - - let mut f = ConsoleLineFormatter::new(4); - f.wrap_width = 12; // Was char wrap. - f.maintain_indent = false; - f.wrap_additional_indent = 0; - - assert_eq!(f.index_set_horizontal_v2d(&b, 0, 0, Floor), 0); - assert_eq!(f.index_set_horizontal_v2d(&b, 0, 11, Floor), 11); - assert_eq!(f.index_set_horizontal_v2d(&b, 0, 12, Floor), 11); - - assert_eq!(f.index_set_horizontal_v2d(&b, 8, 0, Floor), 0); - assert_eq!(f.index_set_horizontal_v2d(&b, 8, 11, Floor), 11); - assert_eq!(f.index_set_horizontal_v2d(&b, 8, 12, Floor), 11); - - assert_eq!(f.index_set_horizontal_v2d(&b, 11, 0, Floor), 0); - assert_eq!(f.index_set_horizontal_v2d(&b, 11, 11, Floor), 11); - assert_eq!(f.index_set_horizontal_v2d(&b, 11, 12, Floor), 11); - - assert_eq!(f.index_set_horizontal_v2d(&b, 12, 0, Floor), 12); - assert_eq!(f.index_set_horizontal_v2d(&b, 12, 11, Floor), 23); - assert_eq!(f.index_set_horizontal_v2d(&b, 12, 12, Floor), 23); - - assert_eq!(f.index_set_horizontal_v2d(&b, 17, 0, Floor), 12); - assert_eq!(f.index_set_horizontal_v2d(&b, 17, 11, Floor), 23); - assert_eq!(f.index_set_horizontal_v2d(&b, 17, 12, Floor), 23); - - assert_eq!(f.index_set_horizontal_v2d(&b, 23, 0, Floor), 12); - assert_eq!(f.index_set_horizontal_v2d(&b, 23, 11, Floor), 23); - assert_eq!(f.index_set_horizontal_v2d(&b, 23, 12, Floor), 23); - } - - #[test] - fn index_offset_vertical_v2d_1() { - let b = Buffer::new_from_str("Hello there, stranger!\nHow are you doing this fine day?"); // 55 graphemes long - - let mut f = ConsoleLineFormatter::new(4); - f.wrap_width = 80; // Was char wrap. - f.maintain_indent = false; - f.wrap_additional_indent = 0; - - assert_eq!(f.index_offset_vertical_v2d(&b, 0, 0, (Floor, Floor)), 0); - assert_eq!(f.index_offset_vertical_v2d(&b, 0, 1, (Floor, Floor)), 23); - assert_eq!(f.index_offset_vertical_v2d(&b, 23, -1, (Floor, Floor)), 0); - - assert_eq!(f.index_offset_vertical_v2d(&b, 2, 0, (Floor, Floor)), 2); - assert_eq!(f.index_offset_vertical_v2d(&b, 2, 1, (Floor, Floor)), 25); - assert_eq!(f.index_offset_vertical_v2d(&b, 25, -1, (Floor, Floor)), 2); - - assert_eq!(f.index_offset_vertical_v2d(&b, 22, 0, (Floor, Floor)), 22); - assert_eq!(f.index_offset_vertical_v2d(&b, 22, 1, (Floor, Floor)), 45); - assert_eq!(f.index_offset_vertical_v2d(&b, 45, -1, (Floor, Floor)), 22); - - assert_eq!(f.index_offset_vertical_v2d(&b, 54, 0, (Floor, Floor)), 54); - assert_eq!(f.index_offset_vertical_v2d(&b, 54, 1, (Floor, Floor)), 55); - assert_eq!(f.index_offset_vertical_v2d(&b, 54, -1, (Floor, Floor)), 22); - } - - #[test] - fn index_offset_vertical_v2d_2() { - let b = Buffer::new_from_str("Hello there, stranger! How are you doing this fine day?"); // 55 graphemes long - - let mut f = ConsoleLineFormatter::new(4); - f.wrap_width = 12; - f.maintain_indent = false; - f.wrap_additional_indent = 0; - - assert_eq!(f.index_offset_vertical_v2d(&b, 0, 0, (Floor, Floor)), 0); - assert_eq!(f.index_offset_vertical_v2d(&b, 0, 1, (Floor, Floor)), 12); - assert_eq!(f.index_offset_vertical_v2d(&b, 0, 2, (Floor, Floor)), 24); - - assert_eq!(f.index_offset_vertical_v2d(&b, 0, 0, (Floor, Floor)), 0); - assert_eq!(f.index_offset_vertical_v2d(&b, 12, -1, (Floor, Floor)), 0); - assert_eq!(f.index_offset_vertical_v2d(&b, 24, -2, (Floor, Floor)), 0); - - assert_eq!(f.index_offset_vertical_v2d(&b, 4, 0, (Floor, Floor)), 4); - assert_eq!(f.index_offset_vertical_v2d(&b, 4, 1, (Floor, Floor)), 16); - assert_eq!(f.index_offset_vertical_v2d(&b, 4, 2, (Floor, Floor)), 28); - - assert_eq!(f.index_offset_vertical_v2d(&b, 4, 0, (Floor, Floor)), 4); - assert_eq!(f.index_offset_vertical_v2d(&b, 16, -1, (Floor, Floor)), 4); - assert_eq!(f.index_offset_vertical_v2d(&b, 28, -2, (Floor, Floor)), 4); - - assert_eq!(f.index_offset_vertical_v2d(&b, 11, 0, (Floor, Floor)), 11); - assert_eq!(f.index_offset_vertical_v2d(&b, 11, 1, (Floor, Floor)), 23); - assert_eq!(f.index_offset_vertical_v2d(&b, 11, 2, (Floor, Floor)), 35); - - assert_eq!(f.index_offset_vertical_v2d(&b, 11, 0, (Floor, Floor)), 11); - assert_eq!(f.index_offset_vertical_v2d(&b, 23, -1, (Floor, Floor)), 11); - assert_eq!(f.index_offset_vertical_v2d(&b, 35, -2, (Floor, Floor)), 11); - } -} +// use std::{borrow::Cow, cmp::max}; + +// use crate::{ +// formatter::{LineFormatter, RoundingBehavior}, +// string_utils::{is_line_ending, is_whitespace}, +// utils::{grapheme_width, RopeGraphemes}, +// }; + +// #[cfg(test)] +// mod tests { +// #![allow(unused_imports)] +// use ropey::Rope; + +// use crate::buffer::Buffer; +// use crate::formatter::LineFormatter; +// use crate::formatter::RoundingBehavior::{Ceiling, Floor, Round}; +// use crate::utils::RopeGraphemes; + +// use super::*; + +// #[test] +// fn dimensions_1() { +// let text = Rope::from_str("Hello there, stranger!"); // 22 graphemes long + +// let mut f = ConsoleLineFormatter::new(4); +// f.wrap_width = 80; +// f.maintain_indent = false; +// f.wrap_additional_indent = 0; + +// assert_eq!(f.dimensions(RopeGraphemes::new(&text.slice(..))), (1, 22)); +// } + +// #[test] +// fn dimensions_3() { +// let text = Rope::from_str("Hello there, stranger! How are you doing this fine day?"); // 56 graphemes long + +// let mut f = ConsoleLineFormatter::new(4); +// f.wrap_width = 12; +// f.maintain_indent = false; +// f.wrap_additional_indent = 0; + +// assert_eq!(f.dimensions(RopeGraphemes::new(&text.slice(..))), (6, 12)); +// } + +// #[test] +// fn dimensions_4() { +// // 55 graphemes long +// let text = Rope::from_str( +// "税マイミ文末\ +// レ日題イぽじ\ +// や男目統ス公\ +// 身みトしつ結\ +// 煮ヱマレ断西\ +// ロ領視りいぽ\ +// 凱字テ式重反\ +// てす献罪がご\ +// く官俵呉嫁ー\ +// 。", +// ); + +// let mut f = ConsoleLineFormatter::new(4); +// f.wrap_width = 12; +// f.maintain_indent = false; +// f.wrap_additional_indent = 0; + +// assert_eq!(f.dimensions(RopeGraphemes::new(&text.slice(..))), (10, 12)); +// } + +// #[test] +// fn dimensions_5() { +// // 55 graphemes long +// let text = Rope::from_str( +// "税マイミ文末\ +// レ日題イぽじ\ +// や男目統ス公\ +// 身みトしつ結\ +// 煮ヱマレ断西\ +// ロ領視りいぽ\ +// 凱字テ式重反\ +// てす献罪がご\ +// く官俵呉嫁ー\ +// 。", +// ); + +// let mut f = ConsoleLineFormatter::new(4); +// f.wrap_width = 12; +// f.maintain_indent = false; +// f.wrap_additional_indent = 0; + +// assert_eq!(f.dimensions(RopeGraphemes::new(&text.slice(..))), (10, 12)); +// } + +// #[test] +// fn index_to_v2d_1() { +// let text = Rope::from_str("Hello there, stranger!"); // 22 graphemes long + +// let mut f = ConsoleLineFormatter::new(4); +// f.wrap_width = 80; +// f.maintain_indent = false; +// f.wrap_additional_indent = 0; + +// assert_eq!( +// f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 0), +// (0, 0) +// ); +// assert_eq!( +// f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 5), +// (0, 5) +// ); +// assert_eq!( +// f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 22), +// (0, 22) +// ); +// assert_eq!( +// f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 23), +// (0, 22) +// ); +// } + +// #[test] +// fn index_to_v2d_2() { +// let text = Rope::from_str("Hello there, stranger! How are you doing this fine day?"); // 56 graphemes long + +// let mut f = ConsoleLineFormatter::new(4); +// f.wrap_width = 12; // Was char wrap. +// f.maintain_indent = false; +// f.wrap_additional_indent = 0; + +// assert_eq!( +// f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 0), +// (0, 0) +// ); +// assert_eq!( +// f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 5), +// (0, 5) +// ); +// assert_eq!( +// f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 11), +// (0, 11) +// ); + +// assert_eq!( +// f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 12), +// (1, 0) +// ); +// assert_eq!( +// f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 15), +// (1, 3) +// ); +// assert_eq!( +// f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 23), +// (1, 11) +// ); + +// assert_eq!( +// f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 24), +// (2, 0) +// ); +// assert_eq!( +// f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 28), +// (2, 4) +// ); +// assert_eq!( +// f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 35), +// (2, 11) +// ); + +// assert_eq!( +// f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 36), +// (3, 0) +// ); +// assert_eq!( +// f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 43), +// (3, 7) +// ); +// assert_eq!( +// f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 47), +// (3, 11) +// ); + +// assert_eq!( +// f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 48), +// (4, 0) +// ); +// assert_eq!( +// f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 50), +// (4, 2) +// ); +// assert_eq!( +// f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 56), +// (4, 8) +// ); + +// assert_eq!( +// f.index_to_v2d(RopeGraphemes::new(&text.slice(..)), 57), +// (4, 8) +// ); +// } + +// #[test] +// fn v2d_to_index_1() { +// let text = Rope::from_str("Hello there, stranger!"); // 22 graphemes long + +// let mut f = ConsoleLineFormatter::new(4); +// f.wrap_width = 80; +// f.maintain_indent = false; +// f.wrap_additional_indent = 0; + +// assert_eq!( +// f.v2d_to_index(RopeGraphemes::new(&text.slice(..)), (0, 0), (Floor, Floor)), +// 0 +// ); +// assert_eq!( +// f.v2d_to_index(RopeGraphemes::new(&text.slice(..)), (0, 5), (Floor, Floor)), +// 5 +// ); +// assert_eq!( +// f.v2d_to_index(RopeGraphemes::new(&text.slice(..)), (0, 22), (Floor, Floor)), +// 22 +// ); +// assert_eq!( +// f.v2d_to_index(RopeGraphemes::new(&text.slice(..)), (0, 23), (Floor, Floor)), +// 22 +// ); +// assert_eq!( +// f.v2d_to_index(RopeGraphemes::new(&text.slice(..)), (1, 0), (Floor, Floor)), +// 22 +// ); +// assert_eq!( +// f.v2d_to_index(RopeGraphemes::new(&text.slice(..)), (1, 1), (Floor, Floor)), +// 22 +// ); +// } + +// #[test] +// fn v2d_to_index_2() { +// let text = Rope::from_str("Hello there, stranger! How are you doing this fine day?"); // 56 graphemes long + +// let mut f = ConsoleLineFormatter::new(4); +// f.wrap_width = 12; // Was char wrap. +// f.maintain_indent = false; +// f.wrap_additional_indent = 0; + +// assert_eq!( +// f.v2d_to_index(RopeGraphemes::new(&text.slice(..)), (0, 0), (Floor, Floor)), +// 0 +// ); +// assert_eq!( +// f.v2d_to_index(RopeGraphemes::new(&text.slice(..)), (0, 11), (Floor, Floor)), +// 11 +// ); +// assert_eq!( +// f.v2d_to_index(RopeGraphemes::new(&text.slice(..)), (0, 12), (Floor, Floor)), +// 11 +// ); + +// assert_eq!( +// f.v2d_to_index(RopeGraphemes::new(&text.slice(..)), (1, 0), (Floor, Floor)), +// 12 +// ); +// assert_eq!( +// f.v2d_to_index(RopeGraphemes::new(&text.slice(..)), (1, 11), (Floor, Floor)), +// 23 +// ); +// assert_eq!( +// f.v2d_to_index(RopeGraphemes::new(&text.slice(..)), (1, 12), (Floor, Floor)), +// 23 +// ); + +// assert_eq!( +// f.v2d_to_index(RopeGraphemes::new(&text.slice(..)), (2, 0), (Floor, Floor)), +// 24 +// ); +// assert_eq!( +// f.v2d_to_index(RopeGraphemes::new(&text.slice(..)), (2, 11), (Floor, Floor)), +// 35 +// ); +// assert_eq!( +// f.v2d_to_index(RopeGraphemes::new(&text.slice(..)), (2, 12), (Floor, Floor)), +// 35 +// ); + +// assert_eq!( +// f.v2d_to_index(RopeGraphemes::new(&text.slice(..)), (3, 0), (Floor, Floor)), +// 36 +// ); +// assert_eq!( +// f.v2d_to_index(RopeGraphemes::new(&text.slice(..)), (3, 11), (Floor, Floor)), +// 47 +// ); +// assert_eq!( +// f.v2d_to_index(RopeGraphemes::new(&text.slice(..)), (3, 12), (Floor, Floor)), +// 47 +// ); + +// assert_eq!( +// f.v2d_to_index(RopeGraphemes::new(&text.slice(..)), (4, 0), (Floor, Floor)), +// 48 +// ); +// assert_eq!( +// f.v2d_to_index(RopeGraphemes::new(&text.slice(..)), (4, 7), (Floor, Floor)), +// 55 +// ); +// assert_eq!( +// f.v2d_to_index(RopeGraphemes::new(&text.slice(..)), (4, 8), (Floor, Floor)), +// 56 +// ); +// assert_eq!( +// f.v2d_to_index(RopeGraphemes::new(&text.slice(..)), (4, 9), (Floor, Floor)), +// 56 +// ); +// } + +// #[test] +// fn index_to_horizontal_v2d_1() { +// let b = Buffer::new_from_str("Hello there, stranger!\nHow are you doing this fine day?"); // 55 graphemes long + +// let mut f = ConsoleLineFormatter::new(4); +// f.wrap_width = 80; +// f.maintain_indent = false; +// f.wrap_additional_indent = 0; + +// assert_eq!(f.index_to_horizontal_v2d(&b, 0), 0); +// assert_eq!(f.index_to_horizontal_v2d(&b, 5), 5); +// assert_eq!(f.index_to_horizontal_v2d(&b, 26), 3); +// assert_eq!(f.index_to_horizontal_v2d(&b, 55), 32); +// assert_eq!(f.index_to_horizontal_v2d(&b, 56), 32); +// } + +// #[test] +// fn index_to_horizontal_v2d_2() { +// let b = Buffer::new_from_str("Hello there, stranger!\nHow are you doing this fine day?"); // 55 graphemes long + +// let mut f = ConsoleLineFormatter::new(4); +// f.wrap_width = 12; // Was char wrap. +// f.maintain_indent = false; +// f.wrap_additional_indent = 0; + +// assert_eq!(f.index_to_horizontal_v2d(&b, 0), 0); +// assert_eq!(f.index_to_horizontal_v2d(&b, 11), 11); + +// assert_eq!(f.index_to_horizontal_v2d(&b, 12), 0); +// assert_eq!(f.index_to_horizontal_v2d(&b, 22), 10); + +// assert_eq!(f.index_to_horizontal_v2d(&b, 23), 0); +// assert_eq!(f.index_to_horizontal_v2d(&b, 34), 11); + +// assert_eq!(f.index_to_horizontal_v2d(&b, 35), 0); +// assert_eq!(f.index_to_horizontal_v2d(&b, 46), 11); + +// assert_eq!(f.index_to_horizontal_v2d(&b, 47), 0); +// assert_eq!(f.index_to_horizontal_v2d(&b, 55), 8); +// assert_eq!(f.index_to_horizontal_v2d(&b, 56), 8); +// } + +// #[test] +// fn index_to_horizontal_v2d_3() { +// let b = Buffer::new_from_str("Hello there, stranger!\nHow are you doing this fine day?"); // 55 graphemes long + +// let mut f = ConsoleLineFormatter::new(4); +// f.wrap_width = 12; +// f.maintain_indent = false; +// f.wrap_additional_indent = 0; + +// assert_eq!(f.index_to_horizontal_v2d(&b, 0), 0); +// assert_eq!(f.index_to_horizontal_v2d(&b, 5), 5); + +// assert_eq!(f.index_to_horizontal_v2d(&b, 6), 0); +// assert_eq!(f.index_to_horizontal_v2d(&b, 12), 6); + +// assert_eq!(f.index_to_horizontal_v2d(&b, 13), 0); +// assert_eq!(f.index_to_horizontal_v2d(&b, 22), 9); + +// assert_eq!(f.index_to_horizontal_v2d(&b, 23), 0); +// assert_eq!(f.index_to_horizontal_v2d(&b, 34), 11); + +// assert_eq!(f.index_to_horizontal_v2d(&b, 35), 0); +// assert_eq!(f.index_to_horizontal_v2d(&b, 45), 10); + +// assert_eq!(f.index_to_horizontal_v2d(&b, 46), 0); +// assert_eq!(f.index_to_horizontal_v2d(&b, 55), 9); +// assert_eq!(f.index_to_horizontal_v2d(&b, 56), 9); +// } + +// #[test] +// fn index_set_horizontal_v2d_1() { +// let b = Buffer::new_from_str("Hello there, stranger!\nHow are you doing this fine day?"); // 55 graphemes long + +// let mut f = ConsoleLineFormatter::new(4); +// f.wrap_width = 80; +// f.maintain_indent = false; +// f.wrap_additional_indent = 0; + +// assert_eq!(f.index_set_horizontal_v2d(&b, 0, 0, Floor), 0); +// assert_eq!(f.index_set_horizontal_v2d(&b, 0, 22, Floor), 22); +// assert_eq!(f.index_set_horizontal_v2d(&b, 0, 23, Floor), 22); + +// assert_eq!(f.index_set_horizontal_v2d(&b, 8, 0, Floor), 0); +// assert_eq!(f.index_set_horizontal_v2d(&b, 8, 22, Floor), 22); +// assert_eq!(f.index_set_horizontal_v2d(&b, 8, 23, Floor), 22); + +// assert_eq!(f.index_set_horizontal_v2d(&b, 22, 0, Floor), 0); +// assert_eq!(f.index_set_horizontal_v2d(&b, 22, 22, Floor), 22); +// assert_eq!(f.index_set_horizontal_v2d(&b, 22, 23, Floor), 22); + +// assert_eq!(f.index_set_horizontal_v2d(&b, 23, 0, Floor), 23); +// assert_eq!(f.index_set_horizontal_v2d(&b, 23, 32, Floor), 55); +// assert_eq!(f.index_set_horizontal_v2d(&b, 23, 33, Floor), 55); + +// assert_eq!(f.index_set_horizontal_v2d(&b, 28, 0, Floor), 23); +// assert_eq!(f.index_set_horizontal_v2d(&b, 28, 32, Floor), 55); +// assert_eq!(f.index_set_horizontal_v2d(&b, 28, 33, Floor), 55); + +// assert_eq!(f.index_set_horizontal_v2d(&b, 55, 0, Floor), 23); +// assert_eq!(f.index_set_horizontal_v2d(&b, 55, 32, Floor), 55); +// assert_eq!(f.index_set_horizontal_v2d(&b, 55, 33, Floor), 55); +// } + +// #[test] +// fn index_set_horizontal_v2d_2() { +// let b = Buffer::new_from_str("Hello there, stranger! How are you doing this fine day?"); // 55 graphemes long + +// let mut f = ConsoleLineFormatter::new(4); +// f.wrap_width = 12; // Was char wrap. +// f.maintain_indent = false; +// f.wrap_additional_indent = 0; + +// assert_eq!(f.index_set_horizontal_v2d(&b, 0, 0, Floor), 0); +// assert_eq!(f.index_set_horizontal_v2d(&b, 0, 11, Floor), 11); +// assert_eq!(f.index_set_horizontal_v2d(&b, 0, 12, Floor), 11); + +// assert_eq!(f.index_set_horizontal_v2d(&b, 8, 0, Floor), 0); +// assert_eq!(f.index_set_horizontal_v2d(&b, 8, 11, Floor), 11); +// assert_eq!(f.index_set_horizontal_v2d(&b, 8, 12, Floor), 11); + +// assert_eq!(f.index_set_horizontal_v2d(&b, 11, 0, Floor), 0); +// assert_eq!(f.index_set_horizontal_v2d(&b, 11, 11, Floor), 11); +// assert_eq!(f.index_set_horizontal_v2d(&b, 11, 12, Floor), 11); + +// assert_eq!(f.index_set_horizontal_v2d(&b, 12, 0, Floor), 12); +// assert_eq!(f.index_set_horizontal_v2d(&b, 12, 11, Floor), 23); +// assert_eq!(f.index_set_horizontal_v2d(&b, 12, 12, Floor), 23); + +// assert_eq!(f.index_set_horizontal_v2d(&b, 17, 0, Floor), 12); +// assert_eq!(f.index_set_horizontal_v2d(&b, 17, 11, Floor), 23); +// assert_eq!(f.index_set_horizontal_v2d(&b, 17, 12, Floor), 23); + +// assert_eq!(f.index_set_horizontal_v2d(&b, 23, 0, Floor), 12); +// assert_eq!(f.index_set_horizontal_v2d(&b, 23, 11, Floor), 23); +// assert_eq!(f.index_set_horizontal_v2d(&b, 23, 12, Floor), 23); +// } + +// #[test] +// fn index_offset_vertical_v2d_1() { +// let b = Buffer::new_from_str("Hello there, stranger!\nHow are you doing this fine day?"); // 55 graphemes long + +// let mut f = ConsoleLineFormatter::new(4); +// f.wrap_width = 80; // Was char wrap. +// f.maintain_indent = false; +// f.wrap_additional_indent = 0; + +// assert_eq!(f.index_offset_vertical_v2d(&b, 0, 0, (Floor, Floor)), 0); +// assert_eq!(f.index_offset_vertical_v2d(&b, 0, 1, (Floor, Floor)), 23); +// assert_eq!(f.index_offset_vertical_v2d(&b, 23, -1, (Floor, Floor)), 0); + +// assert_eq!(f.index_offset_vertical_v2d(&b, 2, 0, (Floor, Floor)), 2); +// assert_eq!(f.index_offset_vertical_v2d(&b, 2, 1, (Floor, Floor)), 25); +// assert_eq!(f.index_offset_vertical_v2d(&b, 25, -1, (Floor, Floor)), 2); + +// assert_eq!(f.index_offset_vertical_v2d(&b, 22, 0, (Floor, Floor)), 22); +// assert_eq!(f.index_offset_vertical_v2d(&b, 22, 1, (Floor, Floor)), 45); +// assert_eq!(f.index_offset_vertical_v2d(&b, 45, -1, (Floor, Floor)), 22); + +// assert_eq!(f.index_offset_vertical_v2d(&b, 54, 0, (Floor, Floor)), 54); +// assert_eq!(f.index_offset_vertical_v2d(&b, 54, 1, (Floor, Floor)), 55); +// assert_eq!(f.index_offset_vertical_v2d(&b, 54, -1, (Floor, Floor)), 22); +// } + +// #[test] +// fn index_offset_vertical_v2d_2() { +// let b = Buffer::new_from_str("Hello there, stranger! How are you doing this fine day?"); // 55 graphemes long + +// let mut f = ConsoleLineFormatter::new(4); +// f.wrap_width = 12; +// f.maintain_indent = false; +// f.wrap_additional_indent = 0; + +// assert_eq!(f.index_offset_vertical_v2d(&b, 0, 0, (Floor, Floor)), 0); +// assert_eq!(f.index_offset_vertical_v2d(&b, 0, 1, (Floor, Floor)), 12); +// assert_eq!(f.index_offset_vertical_v2d(&b, 0, 2, (Floor, Floor)), 24); + +// assert_eq!(f.index_offset_vertical_v2d(&b, 0, 0, (Floor, Floor)), 0); +// assert_eq!(f.index_offset_vertical_v2d(&b, 12, -1, (Floor, Floor)), 0); +// assert_eq!(f.index_offset_vertical_v2d(&b, 24, -2, (Floor, Floor)), 0); + +// assert_eq!(f.index_offset_vertical_v2d(&b, 4, 0, (Floor, Floor)), 4); +// assert_eq!(f.index_offset_vertical_v2d(&b, 4, 1, (Floor, Floor)), 16); +// assert_eq!(f.index_offset_vertical_v2d(&b, 4, 2, (Floor, Floor)), 28); + +// assert_eq!(f.index_offset_vertical_v2d(&b, 4, 0, (Floor, Floor)), 4); +// assert_eq!(f.index_offset_vertical_v2d(&b, 16, -1, (Floor, Floor)), 4); +// assert_eq!(f.index_offset_vertical_v2d(&b, 28, -2, (Floor, Floor)), 4); + +// assert_eq!(f.index_offset_vertical_v2d(&b, 11, 0, (Floor, Floor)), 11); +// assert_eq!(f.index_offset_vertical_v2d(&b, 11, 1, (Floor, Floor)), 23); +// assert_eq!(f.index_offset_vertical_v2d(&b, 11, 2, (Floor, Floor)), 35); + +// assert_eq!(f.index_offset_vertical_v2d(&b, 11, 0, (Floor, Floor)), 11); +// assert_eq!(f.index_offset_vertical_v2d(&b, 23, -1, (Floor, Floor)), 11); +// assert_eq!(f.index_offset_vertical_v2d(&b, 35, -2, (Floor, Floor)), 11); +// } +// } diff --git a/src/term_ui/mod.rs b/src/term_ui/mod.rs index 230a52c..9118f60 100644 --- a/src/term_ui/mod.rs +++ b/src/term_ui/mod.rs @@ -13,15 +13,12 @@ use crossterm::{ use crate::{ editor::Editor, - formatter::{block_count, block_index_and_range, char_range_from_block_index, LineFormatter}, - string_utils::{is_line_ending, line_ending_to_str, LineEnding}, - utils::{digit_count, RopeGraphemes, Timer}, + formatter::LineFormatter, + string_utils::{char_count, is_line_ending, line_ending_to_str, LineEnding}, + utils::{digit_count, Timer}, }; -use self::{ - formatter::ConsoleLineFormatter, - screen::{Screen, Style}, -}; +use self::screen::{Screen, Style}; const EMPTY_MOD: KeyModifiers = KeyModifiers::empty(); const UPDATE_TICK_MS: u64 = 10; @@ -159,10 +156,7 @@ macro_rules! ui_loop { $term_ui .editor .update_dim($term_ui.height - 1, $term_ui.width); - $term_ui - .editor - .formatter - .set_wrap_width($term_ui.editor.view_dim.1); + $term_ui.editor.formatter.wrap_width = $term_ui.editor.view_dim.1; // Draw! { @@ -176,7 +170,7 @@ macro_rules! ui_loop { pub struct TermUI { screen: Screen, - editor: Editor, + editor: Editor, width: usize, height: usize, quit: bool, @@ -190,10 +184,10 @@ enum LoopStatus { impl TermUI { pub fn new() -> TermUI { - TermUI::new_from_editor(Editor::new(ConsoleLineFormatter::new(4))) + TermUI::new_from_editor(Editor::new(LineFormatter::new(4))) } - pub fn new_from_editor(ed: Editor) -> TermUI { + pub fn new_from_editor(ed: Editor) -> TermUI { let (w, h) = crossterm::terminal::size().unwrap(); let mut editor = ed; editor.update_dim(h as usize - 1, w as usize); @@ -216,7 +210,7 @@ impl TermUI { self.width = w as usize; self.height = h as usize; self.editor.update_dim(self.height - 1, self.width); - self.editor.formatter.set_wrap_width(self.editor.view_dim.1); + self.editor.formatter.wrap_width = self.editor.view_dim.1; self.screen.resize(w as usize, h as usize); // Start the UI @@ -444,12 +438,7 @@ impl TermUI { } } - fn draw_editor( - &self, - editor: &Editor, - c1: (usize, usize), - c2: (usize, usize), - ) { + fn draw_editor(&self, editor: &Editor, c1: (usize, usize), c2: (usize, usize)) { // Fill in top row with info line color for i in c1.1..(c2.1 + 1) { self.screen.draw(i, c1.0, " ", STYLE_INFO); @@ -502,23 +491,20 @@ impl TermUI { self.draw_editor_text(editor, (c1.0 + 1, c1.1), c2); } - fn draw_editor_text( - &self, - editor: &Editor, - c1: (usize, usize), - c2: (usize, usize), - ) { + fn draw_editor_text(&self, editor: &Editor, c1: (usize, usize), c2: (usize, usize)) { // 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, col_i) = editor.buffer.index_to_line_col(editor.view_pos.0); - let temp_line = editor.buffer.get_line(line_index); - let (mut line_block_index, block_range) = block_index_and_range(&temp_line, col_i); - let mut char_index = editor.buffer.line_col_to_index((line_index, block_range.0)); - let (vis_line_offset, _) = editor.formatter.index_to_v2d( - RopeGraphemes::new(&temp_line.slice(block_range.0..block_range.1)), - editor.view_pos.0 - char_index, - ); + let line_index = editor.buffer.text.char_to_line(editor.view_pos.0); + + let (blocks_iter, char_offset) = editor.formatter.iter(&editor.buffer, editor.view_pos.0); + + let vis_line_offset = blocks_iter + .clone() + .next() + .unwrap() + .0 + .vpos(editor.view_pos.0); let mut screen_line = c1.0 as isize - vis_line_offset as isize; let screen_col = c1.1 as isize + gutter_width as isize; @@ -528,8 +514,16 @@ impl TermUI { self.screen.draw(c1.1, y, blank_gutter, STYLE_GUTTER_ODD); } + // Loop through the blocks, printing them to the screen. + let mut is_first_loop = true; let mut line_num = line_index + 1; - for line in editor.buffer.line_iter_at_index(line_index) { + let mut char_index = editor.view_pos.0 - char_offset; + for (block_vis_iter, is_line_start) in blocks_iter { + if is_line_start && !is_first_loop { + line_num += 1; + } + is_first_loop = false; + let gutter_style = if (line_num % 2) == 0 { STYLE_GUTTER_EVEN } else { @@ -537,7 +531,7 @@ impl TermUI { }; // Print line number - if line_block_index == 0 { + if is_line_start { let lnx = c1.1; let lny = screen_line as usize; if lny >= c1.0 && lny <= c2.0 { @@ -555,97 +549,63 @@ impl TermUI { } } - // Loop through the graphemes of the line and print them to + // Loop through the graphemes of the block and print them to // the screen. - let max_block_index = block_count(&line) - 1; - let block_range = char_range_from_block_index(&line, line_block_index); - let mut g_iter = editor.formatter.iter(RopeGraphemes::new( - &line.slice(block_range.0..block_range.1), - )); - let mut last_pos_y = 0; - let mut lines_traversed: usize = 0; - loop { - for (g, (pos_y, pos_x), width) in g_iter { - let do_gutter = - last_pos_y != pos_y || (lines_traversed == 0 && line_block_index != 0); - if last_pos_y != pos_y { - if last_pos_y < pos_y { - lines_traversed += pos_y - last_pos_y; + for (g, (pos_y, pos_x), width) in block_vis_iter { + // Calculate the cell coordinates at which to draw the grapheme + if pos_y > last_pos_y { + screen_line += 1; + last_pos_y = pos_y; + } + let px = pos_x as isize + screen_col - editor.view_pos.1 as isize; + let py = screen_line; + + // If we're off the bottom, we're done + if py > c2.0 as isize { + return; + } + + // Draw the grapheme to the screen if it's in bounds + if (px >= c1.1 as isize) && (py >= c1.0 as isize) && (px <= c2.1 as isize) { + // Check if the character is within a cursor + let mut at_cursor = false; + for c in editor.cursors.iter() { + if char_index >= c.range.0 && char_index <= c.range.1 { + at_cursor = true; + self.screen.set_cursor(px as usize, py as usize); } - last_pos_y = pos_y; - } - // Calculate the cell coordinates at which to draw the grapheme - let px = pos_x as isize + screen_col - editor.view_pos.1 as isize; - let py = lines_traversed as isize + screen_line; - - // If we're off the bottom, we're done - if py > c2.0 as isize { - return; } - if do_gutter { - self.screen - .draw(c1.1, py as usize, blank_gutter, gutter_style); - } - - // Draw the grapheme to the screen if it's in bounds - if (px >= c1.1 as isize) && (py >= c1.0 as isize) && (px <= c2.1 as isize) { - // Check if the character is within a cursor - let mut at_cursor = false; - for c in editor.cursors.iter() { - if char_index >= c.range.0 && char_index <= c.range.1 { - at_cursor = true; - self.screen.set_cursor(px as usize, py as usize); + // Actually print the character + if is_line_ending(&g) { + if at_cursor { + self.screen + .draw(px as usize, py as usize, " ", STYLE_CURSOR); + } + } else if g == "\t" { + for i in 0..width { + let tpx = px as usize + i; + if tpx <= c2.1 { + self.screen.draw(tpx as usize, py as usize, " ", STYLE_MAIN); } } - // Actually print the character - if is_line_ending(&g) { - if at_cursor { - self.screen - .draw(px as usize, py as usize, " ", STYLE_CURSOR); - } - } else if g == "\t" { - for i in 0..width { - let tpx = px as usize + i; - if tpx <= c2.1 { - self.screen.draw(tpx as usize, py as usize, " ", STYLE_MAIN); - } - } - - if at_cursor { - self.screen - .draw(px as usize, py as usize, " ", STYLE_CURSOR); - } + if at_cursor { + self.screen + .draw(px as usize, py as usize, " ", STYLE_CURSOR); + } + } else { + if at_cursor { + self.screen.draw(px as usize, py as usize, &g, STYLE_CURSOR); } else { - if at_cursor { - self.screen.draw(px as usize, py as usize, &g, STYLE_CURSOR); - } else { - self.screen.draw(px as usize, py as usize, &g, STYLE_MAIN); - } + self.screen.draw(px as usize, py as usize, &g, STYLE_MAIN); } } - - char_index += g.chars().count(); } - // Move on to the next block. - line_block_index += 1; - if line_block_index <= max_block_index { - let block_range = char_range_from_block_index(&line, line_block_index); - g_iter = editor.formatter.iter(RopeGraphemes::new( - &line.slice(block_range.0..block_range.1), - )); - lines_traversed += 1; - } else { - break; - } + char_index += char_count(&g); } - - line_block_index = 0; - screen_line += lines_traversed as isize + 1; - line_num += 1; } // If we get here, it means we reached the end of the text buffer @@ -664,7 +624,7 @@ impl TermUI { // Calculate the cell coordinates at which to draw the cursor let pos_x = editor .formatter - .index_to_horizontal_v2d(&self.editor.buffer, self.editor.buffer.char_count()); + .get_horizontal(&self.editor.buffer, self.editor.buffer.char_count()); let mut px = pos_x as isize + screen_col - editor.view_pos.1 as isize; let mut py = screen_line - 1; if px > c2.1 as isize { diff --git a/src/utils.rs b/src/utils.rs index 37c135e..ce42422 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -122,6 +122,7 @@ pub fn is_grapheme_boundary(slice: &RopeSlice, char_idx: usize) -> bool { } /// An iterator over the graphemes of a RopeSlice. +#[derive(Clone)] pub struct RopeGraphemes<'a> { text: RopeSlice<'a>, chunks: Chunks<'a>,