Switch to new backend buffer for text, cursor, and undo management.

This commit is contained in:
Nathan Vegdahl 2020-02-23 19:56:08 +09:00
parent 6077be2dfd
commit c4fa72405f
11 changed files with 501 additions and 2037 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,49 +0,0 @@
use std::collections::LinkedList;
/// A text editing operation
#[derive(Clone)]
pub enum Operation {
InsertText(String, usize),
RemoveTextBefore(String, usize),
RemoveTextAfter(String, usize),
MoveText(usize, usize, usize),
CompositeOp(Vec<Operation>),
}
/// An undo/redo stack of text editing operations
pub struct UndoStack {
stack_a: LinkedList<Operation>,
stack_b: LinkedList<Operation>,
}
impl UndoStack {
pub fn new() -> UndoStack {
UndoStack {
stack_a: LinkedList::new(),
stack_b: LinkedList::new(),
}
}
pub fn push(&mut self, op: Operation) {
self.stack_a.push_back(op);
self.stack_b.clear();
}
pub fn prev(&mut self) -> Option<Operation> {
if let Some(op) = self.stack_a.pop_back() {
self.stack_b.push_back(op.clone());
return Some(op);
} else {
return None;
}
}
pub fn next(&mut self) -> Option<Operation> {
if let Some(op) = self.stack_b.pop_back() {
self.stack_a.push_back(op.clone());
return Some(op);
} else {
return None;
}
}
}

View File

@ -1,21 +1,25 @@
#![allow(dead_code)]
mod cursor;
use std::{
cmp::{max, min},
collections::HashMap,
fs::File,
io::{self, BufReader, BufWriter, Write},
path::{Path, PathBuf},
};
use crate::{
buffer::Buffer,
formatter::LineFormatter,
string_utils::{char_count, rope_slice_to_line_ending, LineEnding},
utils::{digit_count, RopeGraphemes},
};
use ropey::Rope;
use self::cursor::CursorSet;
use backend::{buffer::Buffer, marks::Mark};
use crate::{
formatter::LineFormatter,
graphemes::{
is_grapheme_boundary, nth_next_grapheme_boundary, nth_prev_grapheme_boundary, RopeGraphemes,
},
string_utils::{rope_slice_to_line_ending, LineEnding},
utils::digit_count,
};
pub struct Editor {
pub buffer: Buffer,
@ -24,7 +28,6 @@ pub struct Editor {
pub line_ending_type: LineEnding,
pub soft_tabs: bool,
pub soft_tab_width: u8,
pub dirty: bool,
// The dimensions of the total editor in screen space, including the
// header, gutter, etc.
@ -32,76 +35,90 @@ pub struct Editor {
// The dimensions and position of just the text view portion of the editor
pub view_dim: (usize, usize), // (height, width)
pub view_pos: (usize, usize), // (char index, visual horizontal offset)
// The editing cursor position
pub cursors: CursorSet,
// Indices into the mark sets of the buffer.
pub v_msi: usize, // View position MarkSet index.
pub c_msi: usize, // Cursors MarkSet index.
}
impl Editor {
/// Create a new blank editor
pub fn new(formatter: LineFormatter) -> Editor {
let (buffer, v_msi, c_msi) = {
let mut buffer = Buffer::new("".into());
let view_idx = buffer.add_mark_set();
let cursors_idx = buffer.add_mark_set();
buffer.mark_sets[view_idx].add_mark(Mark::new(0, 0));
buffer.mark_sets[cursors_idx].add_mark(Mark::new(0, 0));
(buffer, view_idx, cursors_idx)
};
Editor {
buffer: Buffer::new(),
buffer: buffer,
formatter: formatter,
file_path: PathBuf::new(),
line_ending_type: LineEnding::LF,
soft_tabs: false,
soft_tab_width: 4,
dirty: false,
editor_dim: (0, 0),
view_dim: (0, 0),
view_pos: (0, 0),
cursors: CursorSet::new(),
v_msi: v_msi,
c_msi: c_msi,
}
}
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
_ => panic!("Could not open file!"),
pub fn new_from_file(formatter: LineFormatter, path: &Path) -> io::Result<Editor> {
let (buffer, v_msi, c_msi) = {
let mut buffer = Buffer::new(Rope::from_reader(BufReader::new(File::open(path)?))?);
let view_idx = buffer.add_mark_set();
let cursors_idx = buffer.add_mark_set();
buffer.mark_sets[view_idx].add_mark(Mark::new(0, 0));
buffer.mark_sets[cursors_idx].add_mark(Mark::new(0, 0));
(buffer, view_idx, cursors_idx)
};
let mut ed = Editor {
buffer: buf,
buffer: buffer,
formatter: formatter,
file_path: path.to_path_buf(),
line_ending_type: LineEnding::LF,
soft_tabs: false,
soft_tab_width: 4,
dirty: false,
editor_dim: (0, 0),
view_dim: (0, 0),
view_pos: (0, 0),
cursors: CursorSet::new(),
v_msi: v_msi,
c_msi: c_msi,
};
// For multiple-cursor testing
// let mut cur = Cursor::new();
// cur.range.0 = 30;
// cur.range.1 = 30;
// cur.update_vis_start(&(ed.buffer), &(ed.formatter));
// ed.cursors.add_cursor(cur);
ed.auto_detect_line_ending();
ed.auto_detect_indentation_style();
return ed;
Ok(ed)
}
pub fn save_if_dirty(&mut self) {
if self.dirty && self.file_path != PathBuf::new() {
let _ = self.buffer.save_to_file(&self.file_path);
self.dirty = false;
pub fn save_if_dirty(&mut self) -> io::Result<()> {
if self.buffer.is_dirty && self.file_path != PathBuf::new() {
let mut f = BufWriter::new(File::create(&self.file_path)?);
for c in self.buffer.text.chunks() {
f.write(c.as_bytes())?;
}
self.buffer.is_dirty = false;
}
Ok(())
}
pub fn auto_detect_line_ending(&mut self) {
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.line_iter().take(100) {
for line in self.buffer.text.lines().take(100) {
// Get the line ending
let ending = if line.len_chars() == 1 {
let g = RopeGraphemes::new(&line.slice((line.len_chars() - 1)..))
@ -181,7 +198,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.line_iter().take(1000) {
for line in self.buffer.text.lines().take(1000) {
let mut c_iter = line.chars();
match c_iter.next() {
Some('\t') => {
@ -254,7 +271,7 @@ impl Editor {
/// Updates the view dimensions.
pub fn update_dim(&mut self, h: usize, w: usize) {
let line_count_digits = digit_count(self.buffer.line_count() as u32, 10) as usize;
let line_count_digits = digit_count(self.buffer.text.len_lines() as u32, 10) as usize;
self.editor_dim = (h, w);
// Minus 1 vertically for the header, minus two more than the digits in
@ -267,332 +284,258 @@ impl Editor {
pub fn undo(&mut self) {
// TODO: handle multiple cursors properly
if let Some(pos) = self.buffer.undo() {
self.cursors.truncate(1);
self.cursors[0].range.0 = pos;
self.cursors[0].range.1 = pos;
self.cursors[0].update_vis_start(&(self.buffer), &(self.formatter));
if let Some((_start, end)) = self.buffer.undo() {
self.buffer.mark_sets[self.c_msi].reduce_to_main();
self.buffer.mark_sets[self.c_msi][0].head = end;
self.buffer.mark_sets[self.c_msi][0].tail = end;
self.buffer.mark_sets[self.c_msi][0].hh_pos = None;
self.move_view_to_cursor();
self.dirty = true;
self.cursors.make_consistent();
}
}
pub fn redo(&mut self) {
// TODO: handle multiple cursors properly
if let Some(pos) = self.buffer.redo() {
self.cursors.truncate(1);
self.cursors[0].range.0 = pos;
self.cursors[0].range.1 = pos;
self.cursors[0].update_vis_start(&(self.buffer), &(self.formatter));
if let Some((_start, end)) = self.buffer.redo() {
self.buffer.mark_sets[self.c_msi].reduce_to_main();
self.buffer.mark_sets[self.c_msi][0].head = end;
self.buffer.mark_sets[self.c_msi][0].tail = end;
self.buffer.mark_sets[self.c_msi][0].hh_pos = None;
self.move_view_to_cursor();
self.dirty = true;
self.cursors.make_consistent();
}
}
/// Moves the editor's view the minimum amount to show the cursor
pub fn move_view_to_cursor(&mut self) {
// Find the first and last char index visible within the editor.
let c_first = self
.formatter
.set_horizontal(&self.buffer, self.view_pos.0, 0);
let mut c_last =
self.formatter
.offset_vertical(&self.buffer, c_first, self.view_dim.0 as isize - 1);
let c_first = self.formatter.set_horizontal(
&self.buffer.text,
self.buffer.mark_sets[self.v_msi][0].head,
0,
);
let mut c_last = self.formatter.offset_vertical(
&self.buffer.text,
c_first,
self.view_dim.0 as isize - 1,
);
c_last = self
.formatter
.set_horizontal(&self.buffer, c_last, self.view_dim.1);
.set_horizontal(&self.buffer.text, 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.offset_vertical(
&self.buffer,
self.cursors[0].range.0,
let cursor_head = self.buffer.mark_sets[self.c_msi].main().unwrap().head;
if cursor_head < c_first {
self.buffer.mark_sets[self.v_msi][0].head = cursor_head;
} else if cursor_head > c_last {
self.buffer.mark_sets[self.v_msi][0].head = self.formatter.offset_vertical(
&self.buffer.text,
cursor_head,
-(self.view_dim.0 as isize),
);
}
}
pub fn insert_text_at_cursor(&mut self, text: &str) {
self.cursors.make_consistent();
// TODO: handle multiple cursors.
let mark = self.buffer.mark_sets[self.c_msi].main().unwrap();
let range = mark.range();
let str_len = char_count(text);
let mut offset = 0;
for c in self.cursors.iter_mut() {
// Insert text
self.buffer.insert_text(text, c.range.0 + offset);
self.dirty = true;
// Move cursor
c.range.0 += str_len + offset;
c.range.1 += str_len + offset;
c.update_vis_start(&(self.buffer), &(self.formatter));
// Update offset
offset += str_len;
}
self.buffer.edit((range.start, range.end), text);
// Adjust view
self.move_view_to_cursor();
}
pub fn insert_tab_at_cursor(&mut self) {
self.cursors.make_consistent();
// TODO: handle multiple cursors.
let mark = self.buffer.mark_sets[self.c_msi].main().unwrap();
let range = mark.range();
if self.soft_tabs {
let mut offset = 0;
// Figure out how many spaces to insert
let vis_pos = self
.formatter
.get_horizontal(&self.buffer.text, range.start);
// TODO: handle tab settings
let next_tab_stop =
((vis_pos / self.soft_tab_width as usize) + 1) * self.soft_tab_width as usize;
let space_count = min(next_tab_stop - vis_pos, 8);
for c in self.cursors.iter_mut() {
// Update cursor with offset
c.range.0 += offset;
c.range.1 += offset;
// Figure out how many spaces to insert
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;
let space_count = min(next_tab_stop - vis_pos, 8);
// Insert spaces
let space_strs = [
"", " ", " ", " ", " ", " ", " ", " ", " ",
];
self.buffer.insert_text(space_strs[space_count], c.range.0);
self.dirty = true;
// Move cursor
c.range.0 += space_count;
c.range.1 += space_count;
c.update_vis_start(&(self.buffer), &(self.formatter));
// Update offset
offset += space_count;
}
// Adjust view
self.move_view_to_cursor();
// Insert spaces
let space_strs = [
"", " ", " ", " ", " ", " ", " ", " ", " ",
];
self.buffer
.edit((range.start, range.end), space_strs[space_count]);
} else {
self.insert_text_at_cursor("\t");
self.buffer.edit((range.start, range.end), "\t");
}
}
pub fn backspace_at_cursor(&mut self) {
self.remove_text_behind_cursor(1);
// Adjust view
self.move_view_to_cursor();
}
pub fn remove_text_behind_cursor(&mut self, grapheme_count: usize) {
self.cursors.make_consistent();
// TODO: handle multiple cursors.
let mark = self.buffer.mark_sets[self.c_msi].main().unwrap();
let range = mark.range();
let mut offset = 0;
for c in self.cursors.iter_mut() {
// Update cursor with offset
c.range.0 -= offset;
c.range.1 -= offset;
// Do nothing if there's nothing to delete.
if c.range.0 == 0 {
continue;
}
let len = c.range.0 - self.buffer.nth_prev_grapheme(c.range.0, grapheme_count);
// Remove text
self.buffer.remove_text_before(c.range.0, len);
self.dirty = true;
// Move cursor
c.range.0 -= len;
c.range.1 -= len;
c.update_vis_start(&(self.buffer), &(self.formatter));
// Update offset
offset += len;
// Do nothing if there's nothing to delete.
if range.start == 0 {
return;
}
self.cursors.make_consistent();
let pre =
nth_prev_grapheme_boundary(&self.buffer.text.slice(..), range.start, grapheme_count);
// Remove text
self.buffer.edit((pre, range.start), "");
// Adjust view
self.move_view_to_cursor();
}
pub fn remove_text_in_front_of_cursor(&mut self, grapheme_count: usize) {
self.cursors.make_consistent();
// TODO: handle multiple cursors.
let mark = self.buffer.mark_sets[self.c_msi].main().unwrap();
let range = mark.range();
let mut offset = 0;
for c in self.cursors.iter_mut() {
// Update cursor with offset
c.range.0 -= min(c.range.0, offset);
c.range.1 -= min(c.range.1, offset);
// Do nothing if there's nothing to delete.
if c.range.1 == self.buffer.char_count() {
return;
}
let len = self.buffer.nth_next_grapheme(c.range.1, grapheme_count) - c.range.1;
// Remove text
self.buffer.remove_text_after(c.range.1, len);
self.dirty = true;
// Move cursor
c.update_vis_start(&(self.buffer), &(self.formatter));
// Update offset
offset += len;
// Do nothing if there's nothing to delete.
if range.end == self.buffer.text.len_chars() {
return;
}
self.cursors.make_consistent();
let post =
nth_next_grapheme_boundary(&self.buffer.text.slice(..), range.end, grapheme_count);
// Remove text
self.buffer.edit((range.end, post), "");
// Adjust view
self.move_view_to_cursor();
}
pub fn remove_text_inside_cursor(&mut self) {
self.cursors.make_consistent();
// TODO: handle multiple cursors.
let mark = self.buffer.mark_sets[self.c_msi].main().unwrap();
let range = mark.range();
let mut offset = 0;
for c in self.cursors.iter_mut() {
// Update cursor with offset
c.range.0 -= min(c.range.0, offset);
c.range.1 -= min(c.range.1, offset);
// If selection, remove text
if c.range.0 < c.range.1 {
let len = c.range.1 - c.range.0;
self.buffer
.remove_text_before(c.range.0, c.range.1 - c.range.0);
self.dirty = true;
// Move cursor
c.range.1 = c.range.0;
// Update offset
offset += len;
}
c.update_vis_start(&(self.buffer), &(self.formatter));
if range.start < range.end {
self.buffer.edit((range.start, range.end), "");
}
self.cursors.make_consistent();
// Adjust view
self.move_view_to_cursor();
}
pub fn cursor_to_beginning_of_buffer(&mut self) {
self.cursors = CursorSet::new();
self.buffer.mark_sets[self.c_msi].clear();
self.buffer.mark_sets[self.c_msi].add_mark(Mark::new(0, 0));
self.cursors[0].range = (0, 0);
self.cursors[0].update_vis_start(&(self.buffer), &(self.formatter));
// Adjust view
// Adjust view.
self.move_view_to_cursor();
}
pub fn cursor_to_end_of_buffer(&mut self) {
let end = self.buffer.char_count();
let end = self.buffer.text.len_chars();
self.cursors = CursorSet::new();
self.cursors[0].range = (end, end);
self.cursors[0].update_vis_start(&(self.buffer), &(self.formatter));
self.buffer.mark_sets[self.c_msi].clear();
self.buffer.mark_sets[self.c_msi].add_mark(Mark::new(end, end));
// Adjust view
// Adjust view.
self.move_view_to_cursor();
}
pub fn cursor_left(&mut self, n: usize) {
for c in self.cursors.iter_mut() {
c.range.0 = self.buffer.nth_prev_grapheme(c.range.0, n);
c.range.1 = c.range.0;
c.update_vis_start(&(self.buffer), &(self.formatter));
for mark in self.buffer.mark_sets[self.c_msi].iter_mut() {
mark.head = nth_prev_grapheme_boundary(&self.buffer.text.slice(..), mark.head, n);
mark.tail = mark.head;
mark.hh_pos = None;
}
self.buffer.mark_sets[self.c_msi].make_consistent();
// Adjust view
self.move_view_to_cursor();
}
pub fn cursor_right(&mut self, n: usize) {
for c in self.cursors.iter_mut() {
c.range.1 = self.buffer.nth_next_grapheme(c.range.1, n);
c.range.0 = c.range.1;
c.update_vis_start(&(self.buffer), &(self.formatter));
for mark in self.buffer.mark_sets[self.c_msi].iter_mut() {
mark.head = nth_next_grapheme_boundary(&self.buffer.text.slice(..), mark.head, n);
mark.tail = mark.head;
mark.hh_pos = None;
}
self.buffer.mark_sets[self.c_msi].make_consistent();
// Adjust view
self.move_view_to_cursor();
}
pub fn cursor_up(&mut self, n: usize) {
for c in self.cursors.iter_mut() {
for mark in self.buffer.mark_sets[self.c_msi].iter_mut() {
if mark.hh_pos == None {
mark.hh_pos = Some(self.formatter.get_horizontal(&self.buffer.text, mark.head));
}
let vmove = -1 * n as isize;
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);
let mut temp_index =
self.formatter
.offset_vertical(&self.buffer.text, mark.head, vmove);
temp_index =
self.formatter
.set_horizontal(&self.buffer.text, temp_index, mark.hh_pos.unwrap());
if !self.buffer.is_grapheme(temp_index) {
temp_index = self.buffer.nth_prev_grapheme(temp_index, 1);
if !is_grapheme_boundary(&self.buffer.text.slice(..), temp_index) {
temp_index = nth_prev_grapheme_boundary(&self.buffer.text.slice(..), temp_index, 1);
}
if temp_index == c.range.0 {
if temp_index == mark.head {
// We were already at the top.
c.range.0 = 0;
c.range.1 = 0;
c.update_vis_start(&(self.buffer), &(self.formatter));
mark.head = 0;
mark.tail = 0;
mark.hh_pos = None;
} else {
c.range.0 = temp_index;
c.range.1 = temp_index;
mark.head = temp_index;
mark.tail = temp_index;
}
}
self.buffer.mark_sets[self.c_msi].make_consistent();
// Adjust view
self.move_view_to_cursor();
}
pub fn cursor_down(&mut self, n: usize) {
for c in self.cursors.iter_mut() {
for mark in self.buffer.mark_sets[self.c_msi].iter_mut() {
if mark.hh_pos == None {
mark.hh_pos = Some(self.formatter.get_horizontal(&self.buffer.text, mark.head));
}
let vmove = n as isize;
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);
let mut temp_index =
self.formatter
.offset_vertical(&self.buffer.text, mark.head, vmove);
temp_index =
self.formatter
.set_horizontal(&self.buffer.text, temp_index, mark.hh_pos.unwrap());
if !self.buffer.is_grapheme(temp_index) {
temp_index = self.buffer.nth_prev_grapheme(temp_index, 1);
if !is_grapheme_boundary(&self.buffer.text.slice(..), temp_index) {
temp_index = nth_prev_grapheme_boundary(&self.buffer.text.slice(..), temp_index, 1);
}
if temp_index == c.range.0 {
if temp_index == mark.head {
// We were already at the bottom.
c.range.0 = self.buffer.char_count();
c.range.1 = self.buffer.char_count();
c.update_vis_start(&(self.buffer), &(self.formatter));
mark.head = self.buffer.text.len_chars();
mark.tail = self.buffer.text.len_chars();
mark.hh_pos = None;
} else {
c.range.0 = temp_index;
c.range.1 = temp_index;
mark.head = temp_index;
mark.tail = temp_index;
}
}
self.buffer.mark_sets[self.c_msi].make_consistent();
// Adjust view
self.move_view_to_cursor();
@ -600,9 +543,9 @@ 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.offset_vertical(
&self.buffer,
self.view_pos.0,
self.buffer.mark_sets[self.v_msi][0].head = self.formatter.offset_vertical(
&self.buffer.text,
self.buffer.mark_sets[self.v_msi][0].head,
-1 * move_amount as isize,
);
@ -614,9 +557,11 @@ 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
.offset_vertical(&self.buffer, self.view_pos.0, move_amount as isize);
self.buffer.mark_sets[self.v_msi][0].head = self.formatter.offset_vertical(
&self.buffer.text,
self.buffer.mark_sets[self.v_msi][0].head,
move_amount as isize,
);
self.cursor_down(move_amount);
@ -625,12 +570,23 @@ 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
.set_horizontal(&self.buffer, pos, self.cursors[0].vis_start);
self.cursors[0].range.1 = self.cursors[0].range.0;
self.buffer.mark_sets[self.c_msi].reduce_to_main();
if self.buffer.mark_sets[self.c_msi][0].hh_pos == None {
self.buffer.mark_sets[self.c_msi][0].hh_pos = Some(
self.formatter
.get_horizontal(&self.buffer.text, self.buffer.mark_sets[self.c_msi][0].head),
);
}
let pos = self.buffer.text.line_to_char(n);
let pos = self.formatter.set_horizontal(
&self.buffer.text,
pos,
self.buffer.mark_sets[self.c_msi][0].hh_pos.unwrap(),
);
self.buffer.mark_sets[self.c_msi][0].head = pos;
self.buffer.mark_sets[self.c_msi][0].tail = pos;
// Adjust view
self.move_view_to_cursor();

View File

@ -3,10 +3,9 @@ use std::borrow::Cow;
use ropey::{Rope, RopeSlice};
use crate::{
buffer::Buffer,
graphemes::{grapheme_width, is_grapheme_boundary, prev_grapheme_boundary, RopeGraphemes},
string_utils::char_count,
string_utils::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.
@ -42,10 +41,14 @@ 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
/// relative to the start of the first block.
pub fn iter<'b>(&'b self, buf: &'b Buffer, char_idx: usize) -> (Blocks<'b>, usize) {
pub fn iter<'b>(&'b self, buf: &'b Rope, 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);
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);
(line_idx, col_idx)
};
let line = buf.line(line_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);
@ -54,7 +57,7 @@ impl LineFormatter {
(
Blocks {
formatter: self,
buf: &buf.text,
buf: buf,
line_idx: line_i,
line_block_count: block_count(&line),
block_idx: block_index,
@ -63,8 +66,8 @@ impl LineFormatter {
)
}
/// Converts from char index to the horizontal 2d char index.
pub fn get_horizontal(&self, buf: &Buffer, char_idx: usize) -> usize {
/// 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);
// Traverse the iterator and find the horizontal position of the char
@ -92,7 +95,7 @@ impl LineFormatter {
/// returns a char 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: &Buffer, char_idx: usize, horizontal: usize) -> usize {
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);
let mut hpos_char_idx = None;
@ -134,7 +137,7 @@ impl LineFormatter {
// 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.text.len_chars() == end_i {
if buf.len_chars() == end_i {
return end_i;
} else {
return end_last_i;
@ -143,7 +146,7 @@ impl LineFormatter {
/// 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 {
pub fn offset_vertical(&self, buf: &Rope, char_idx: usize, v_offset: isize) -> usize {
let mut char_idx = char_idx;
let mut v_offset = v_offset;
while v_offset != 0 {
@ -171,9 +174,9 @@ impl LineFormatter {
}
} else if offset_char_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.text.len_chars());
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.text.len_chars() {
if char_idx == buf.len_chars() {
break;
}
} else {
@ -230,13 +233,13 @@ impl LineFormatter {
/// char's offset within that iter.
fn block_vis_iter_and_char_offset<'b>(
&self,
buf: &'b Buffer,
buf: &'b Rope,
char_idx: usize,
) -> (RopeSlice<'b>, BlockVisIter<'b>, usize) {
let line_i = buf.text.char_to_line(char_idx);
let line_start = buf.text.line_to_char(line_i);
let line_end = buf.text.line_to_char(line_i + 1);
let line = buf.text.slice(line_start..line_end);
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 = 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);

212
src/graphemes.rs Normal file
View File

@ -0,0 +1,212 @@
use ropey::{iter::Chunks, str_utils::byte_to_char_idx, RopeSlice};
use unicode_segmentation::{GraphemeCursor, GraphemeIncomplete};
use unicode_width::UnicodeWidthStr;
pub fn grapheme_width(g: &str) -> usize {
if g.as_bytes()[0] <= 127 {
// Fast-path ascii.
// Point 1: theoretically, ascii control characters should have zero
// width, but in our case we actually want them to have width: if they
// show up in text, we want to treat them as textual elements that can
// be editied. So we can get away with making all ascii single width
// here.
// Point 2: we're only examining the first codepoint here, which means
// we're ignoring graphemes formed with combining characters. However,
// if it starts with ascii, it's going to be a single-width grapeheme
// regardless, so, again, we can get away with that here.
// Point 3: we're only examining the first _byte_. But for utf8, when
// checking for ascii range values only, that works.
1
} else {
// We use max(1) here because all grapeheme clusters--even illformed
// ones--should have at least some width so they can be edited
// properly.
UnicodeWidthStr::width(g).max(1)
}
}
pub fn nth_prev_grapheme_boundary(slice: &RopeSlice, char_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;
for _ in 0..n {
char_idx = prev_grapheme_boundary(slice, char_idx);
}
char_idx
}
/// Finds the previous grapheme boundary before the given char position.
pub fn prev_grapheme_boundary(slice: &RopeSlice, char_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);
// 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);
// Set up the grapheme cursor.
let mut gc = GraphemeCursor::new(byte_idx, slice.len_bytes(), true);
// Find the previous grapheme cluster boundary.
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;
}
Err(GraphemeIncomplete::PrevChunk) => {
let (a, b, c, _) = slice.chunk_at_byte(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;
gc.provide_context(ctx_chunk, n - ctx_chunk.len());
}
_ => unreachable!(),
}
}
}
pub fn nth_next_grapheme_boundary(slice: &RopeSlice, char_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;
for _ in 0..n {
char_idx = next_grapheme_boundary(slice, char_idx);
}
char_idx
}
/// Finds the next grapheme boundary after the given char position.
pub fn next_grapheme_boundary(slice: &RopeSlice, char_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);
// 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);
// Set up the grapheme cursor.
let mut gc = GraphemeCursor::new(byte_idx, slice.len_bytes(), true);
// 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;
}
Err(GraphemeIncomplete::NextChunk) => {
chunk_byte_idx += chunk.len();
let (a, _, c, _) = slice.chunk_at_byte(chunk_byte_idx);
chunk = a;
chunk_char_idx = c;
}
Err(GraphemeIncomplete::PreContext(n)) => {
let ctx_chunk = slice.chunk_at_byte(n - 1).0;
gc.provide_context(ctx_chunk, n - ctx_chunk.len());
}
_ => unreachable!(),
}
}
}
/// Returns whether the given char position is a grapheme boundary.
pub fn is_grapheme_boundary(slice: &RopeSlice, char_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);
// Get the chunk with our byte index in it.
let (chunk, chunk_byte_idx, _, _) = slice.chunk_at_byte(byte_idx);
// Set up the grapheme cursor.
let mut gc = GraphemeCursor::new(byte_idx, slice.len_bytes(), true);
// Determine if the given position is a grapheme cluster boundary.
loop {
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);
gc.provide_context(ctx_chunk, ctx_byte_start);
}
_ => unreachable!(),
}
}
}
/// An iterator over the graphemes of a RopeSlice.
#[derive(Clone)]
pub struct RopeGraphemes<'a> {
text: RopeSlice<'a>,
chunks: Chunks<'a>,
cur_chunk: &'a str,
cur_chunk_start: usize,
cursor: GraphemeCursor,
}
impl<'a> RopeGraphemes<'a> {
pub fn new<'b>(slice: &RopeSlice<'b>) -> RopeGraphemes<'b> {
let mut chunks = slice.chunks();
let first_chunk = chunks.next().unwrap_or("");
RopeGraphemes {
text: *slice,
chunks: chunks,
cur_chunk: first_chunk,
cur_chunk_start: 0,
cursor: GraphemeCursor::new(0, slice.len_bytes(), true),
}
}
}
impl<'a> Iterator for RopeGraphemes<'a> {
type Item = RopeSlice<'a>;
fn next(&mut self) -> Option<RopeSlice<'a>> {
let a = self.cursor.cur_cursor();
let b;
loop {
match self
.cursor
.next_boundary(self.cur_chunk, self.cur_chunk_start)
{
Ok(None) => {
return None;
}
Ok(Some(n)) => {
b = n;
break;
}
Err(GraphemeIncomplete::NextChunk) => {
self.cur_chunk_start += self.cur_chunk.len();
self.cur_chunk = self.chunks.next().unwrap_or("");
}
_ => unreachable!(),
}
}
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())
}
}
}

View File

@ -5,9 +5,9 @@ use editor::Editor;
use formatter::LineFormatter;
use term_ui::TermUI;
mod buffer;
mod editor;
mod formatter;
mod graphemes;
mod string_utils;
mod term_ui;
mod utils;
@ -30,6 +30,7 @@ fn main() {
// Load file, if specified
let editor = if let Some(filepath) = args.value_of("file") {
Editor::new_from_file(LineFormatter::new(4), &Path::new(&filepath[..]))
.expect(&format!("Couldn't open file '{}'.", filepath))
} else {
Editor::new(LineFormatter::new(4))
};

View File

@ -243,7 +243,7 @@ impl TermUI {
code: KeyCode::Char('s'),
modifiers: KeyModifiers::CONTROL,
} => {
self.editor.save_if_dirty();
self.editor.save_if_dirty().expect("For some reason the file couldn't be saved. Also, TODO: this code path shouldn't panic.");
}
KeyEvent {
@ -342,7 +342,7 @@ impl TermUI {
code: KeyCode::Backspace,
modifiers: EMPTY_MOD,
} => {
self.editor.backspace_at_cursor();
self.editor.remove_text_behind_cursor(1);
}
KeyEvent {
@ -464,16 +464,17 @@ impl TermUI {
// Filename and dirty marker
let filename = editor.file_path.display();
let dirty_char = if editor.dirty { "*" } else { "" };
let dirty_char = if editor.buffer.is_dirty { "*" } else { "" };
let name = format!("{}{}", filename, dirty_char);
self.screen.draw(c1.1 + 1, c1.0, &name[..], STYLE_INFO);
// 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.char_count() > 0 {
(((editor.cursors[0].range.0 as f32) / (editor.buffer.char_count() as f32)) * 100.0)
as usize
let percentage: usize = if editor.buffer.text.len_chars() > 0 {
(((editor.buffer.mark_sets[editor.c_msi].main().unwrap().head as f32)
/ (editor.buffer.text.len_chars() as f32))
* 100.0) as usize
} else {
100
};
@ -510,12 +511,15 @@ impl TermUI {
}
fn draw_editor_text(&self, editor: &Editor, c1: (usize, usize), c2: (usize, usize)) {
let view_pos = editor.buffer.mark_sets[editor.v_msi][0].head;
let cursors = &editor.buffer.mark_sets[editor.c_msi];
// 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(editor.view_pos.0);
let line_index = editor.buffer.text.char_to_line(view_pos);
let (blocks_iter, char_offset) = editor.formatter.iter(&editor.buffer, editor.view_pos.0);
let (blocks_iter, char_offset) = editor.formatter.iter(&editor.buffer.text, view_pos);
let vis_line_offset = blocks_iter.clone().next().unwrap().0.vpos(char_offset);
@ -537,7 +541,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 = editor.view_pos.0 - char_offset;
let mut char_index = view_pos - char_offset;
for (block_vis_iter, is_line_start) in blocks_iter {
if is_line_start && !is_first_loop {
line_num += 1;
@ -578,7 +582,7 @@ impl TermUI {
screen_line += 1;
last_pos_y = pos_y;
}
let px = pos_x as isize + screen_col - editor.view_pos.1 as isize;
let px = pos_x as isize + screen_col;
let py = screen_line;
// If we're off the bottom, we're done
@ -590,8 +594,8 @@ impl TermUI {
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 {
for c in cursors.iter() {
if char_index >= c.range().start && char_index <= c.range().end {
at_cursor = true;
self.screen.set_cursor(px as usize, py as usize);
}
@ -636,18 +640,19 @@ impl TermUI {
// 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 {
for c in cursors.iter() {
if char_index >= c.range().start && char_index <= c.range().end {
at_cursor = true;
}
}
if at_cursor {
// Calculate the cell coordinates at which to draw the cursor
let pos_x = editor
.formatter
.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 pos_x = editor.formatter.get_horizontal(
&self.editor.buffer.text,
self.editor.buffer.text.len_chars(),
);
let mut px = pos_x as isize + screen_col;
let mut py = screen_line - 1;
if px > c2.1 as isize {
px = c1.1 as isize + screen_col;

View File

@ -1,7 +1,4 @@
use ropey::{iter::Chunks, str_utils::byte_to_char_idx, RopeSlice};
use time::Instant;
use unicode_segmentation::{GraphemeCursor, GraphemeIncomplete};
use unicode_width::UnicodeWidthStr;
pub fn digit_count(mut n: u32, b: u32) -> u32 {
let mut d = 0;
@ -14,197 +11,6 @@ pub fn digit_count(mut n: u32, b: u32) -> u32 {
}
}
//=============================================================
pub fn grapheme_width(g: &str) -> usize {
if g.as_bytes()[0] <= 127 {
// Fast-path ascii.
// Point 1: theoretically, ascii control characters should have zero
// width, but in our case we actually want them to have width: if they
// show up in text, we want to treat them as textual elements that can
// be editied. So we can get away with making all ascii single width
// here.
// Point 2: we're only examining the first codepoint here, which means
// we're ignoring graphemes formed with combining characters. However,
// if it starts with ascii, it's going to be a single-width grapeheme
// regardless, so, again, we can get away with that here.
// Point 3: we're only examining the first _byte_. But for utf8, when
// checking for ascii range values only, that works.
1
} else {
// We use max(1) here because all grapeheme clusters--even illformed
// ones--should have at least some width so they can be edited
// properly.
UnicodeWidthStr::width(g).max(1)
}
}
/// Finds the previous grapheme boundary before the given char position.
pub fn prev_grapheme_boundary(slice: &RopeSlice, char_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);
// 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);
// Set up the grapheme cursor.
let mut gc = GraphemeCursor::new(byte_idx, slice.len_bytes(), true);
// Find the previous grapheme cluster boundary.
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;
}
Err(GraphemeIncomplete::PrevChunk) => {
let (a, b, c, _) = slice.chunk_at_byte(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;
gc.provide_context(ctx_chunk, n - ctx_chunk.len());
}
_ => unreachable!(),
}
}
}
/// Finds the next grapheme boundary after the given char position.
pub fn next_grapheme_boundary(slice: &RopeSlice, char_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);
// 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);
// Set up the grapheme cursor.
let mut gc = GraphemeCursor::new(byte_idx, slice.len_bytes(), true);
// 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;
}
Err(GraphemeIncomplete::NextChunk) => {
chunk_byte_idx += chunk.len();
let (a, _, c, _) = slice.chunk_at_byte(chunk_byte_idx);
chunk = a;
chunk_char_idx = c;
}
Err(GraphemeIncomplete::PreContext(n)) => {
let ctx_chunk = slice.chunk_at_byte(n - 1).0;
gc.provide_context(ctx_chunk, n - ctx_chunk.len());
}
_ => unreachable!(),
}
}
}
/// Returns whether the given char position is a grapheme boundary.
pub fn is_grapheme_boundary(slice: &RopeSlice, char_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);
// Get the chunk with our byte index in it.
let (chunk, chunk_byte_idx, _, _) = slice.chunk_at_byte(byte_idx);
// Set up the grapheme cursor.
let mut gc = GraphemeCursor::new(byte_idx, slice.len_bytes(), true);
// Determine if the given position is a grapheme cluster boundary.
loop {
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);
gc.provide_context(ctx_chunk, ctx_byte_start);
}
_ => unreachable!(),
}
}
}
/// An iterator over the graphemes of a RopeSlice.
#[derive(Clone)]
pub struct RopeGraphemes<'a> {
text: RopeSlice<'a>,
chunks: Chunks<'a>,
cur_chunk: &'a str,
cur_chunk_start: usize,
cursor: GraphemeCursor,
}
impl<'a> RopeGraphemes<'a> {
pub fn new<'b>(slice: &RopeSlice<'b>) -> RopeGraphemes<'b> {
let mut chunks = slice.chunks();
let first_chunk = chunks.next().unwrap_or("");
RopeGraphemes {
text: *slice,
chunks: chunks,
cur_chunk: first_chunk,
cur_chunk_start: 0,
cursor: GraphemeCursor::new(0, slice.len_bytes(), true),
}
}
}
impl<'a> Iterator for RopeGraphemes<'a> {
type Item = RopeSlice<'a>;
fn next(&mut self) -> Option<RopeSlice<'a>> {
let a = self.cursor.cur_cursor();
let b;
loop {
match self
.cursor
.next_boundary(self.cur_chunk, self.cur_chunk_start)
{
Ok(None) => {
return None;
}
Ok(Some(n)) => {
b = n;
break;
}
Err(GraphemeIncomplete::NextChunk) => {
self.cur_chunk_start += self.cur_chunk.len();
self.cur_chunk = self.chunks.next().unwrap_or("");
}
_ => unreachable!(),
}
}
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())
}
}
}
//=============================================================
#[derive(Copy, Clone)]
pub struct Timer {
last_instant: Instant,

View File

@ -61,7 +61,7 @@ impl Buffer {
*mark = mark.edit((start, end), post_len);
}
mark_set.merge_touching();
mark_set.make_consistent();
}
// Do removal if needed.
@ -92,7 +92,7 @@ impl Buffer {
*mark = mark.edit((start, end), post_len);
}
mark_set.merge_touching();
mark_set.make_consistent();
}
// Do removal if needed.
@ -128,7 +128,7 @@ impl Buffer {
*mark = mark.edit((start, end), post_len);
}
mark_set.merge_touching();
mark_set.make_consistent();
}
// Do removal if needed.

View File

@ -15,6 +15,7 @@ impl History {
pub fn push_edit(&mut self, edit: Edit) {
self.edits.truncate(self.position);
self.edits.push(edit);
self.position += 1;
}
pub fn undo(&mut self) -> Option<&Edit> {

View File

@ -111,7 +111,7 @@ impl Mark {
/// and code that modifies a MarkSet should ensure that the invariants remain
/// true.
///
/// The `merge_touching` method will ensure that all expected invariants hold,
/// The `make_consistent` method will ensure that all expected invariants hold,
/// modifying the set to meet the invariants if needed.
#[derive(Debug, Clone)]
pub struct MarkSet {
@ -133,6 +133,23 @@ impl MarkSet {
self.marks.clear();
}
pub fn truncate(&mut self, len: usize) {
self.marks.truncate(len);
self.main_mark_idx = self.main_mark_idx.min(self.marks.len().saturating_sub(1));
}
/// Returns the main mark, if it exists.
pub fn main(&self) -> Option<Mark> {
self.marks.get(self.main_mark_idx).map(|m| *m)
}
/// Removes all marks except the main one.
pub fn reduce_to_main(&mut self) {
self.marks.swap(0, self.main_mark_idx);
self.main_mark_idx = 0;
self.marks.truncate(1);
}
/// Adds a new mark to the set, inserting it into its sorted position, and
/// returns the index where it was inserted.
///
@ -140,7 +157,7 @@ impl MarkSet {
/// range.
///
/// This does *not* preserve disjointedness. You should call
/// `merge_touching` after you have added all the marks you want.
/// `make_consistent` after you have added all the marks you want.
///
/// Runs in O(N + log N) time worst-case, but when the new mark is
/// inserted at the end of the set it is amortized O(1).
@ -185,7 +202,7 @@ impl MarkSet {
/// into one.
///
/// Runs in O(N) time.
pub fn merge_touching(&mut self) {
pub fn make_consistent(&mut self) {
let mut i1 = 0;
let mut i2 = 1;