mirror of https://github.com/andrewkdinh/via.git
Finished add, delete, undo, & redo
This commit is contained in:
parent
fa62deacac
commit
cbeabc55bb
|
@ -14,12 +14,20 @@ pub(crate) struct Editor {
|
|||
eof_reached: bool,
|
||||
/// Configured options
|
||||
options: EditorOptions,
|
||||
/// Represents each line of the editor, and how many characters are in that line
|
||||
lines: Vec<usize>,
|
||||
}
|
||||
|
||||
impl Editor {
|
||||
/// Initialize a new editor with a piece table and reader
|
||||
pub(crate) fn new(piece_table: super::piece_table::PieceTable, reader: BufReader<File>, eof_reached: bool, options: EditorOptions) -> Editor {
|
||||
Editor {piece_table: piece_table, reader: reader, eof_reached: eof_reached, options: options}
|
||||
Editor {piece_table: piece_table,
|
||||
reader: reader,
|
||||
eof_reached:
|
||||
eof_reached,
|
||||
options: options,
|
||||
lines: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Read to end of the file and add it to `piece_table.original_buffer`
|
||||
|
@ -36,18 +44,26 @@ impl Editor {
|
|||
final_str.push_str(&temp_str);
|
||||
temp_str.clear();
|
||||
}
|
||||
self.piece_table.original_buffer.update_add(final_str);
|
||||
self.piece_table.original_buffer.push_str(&final_str);
|
||||
}
|
||||
|
||||
/// Returns the file name
|
||||
fn file_name(&self) -> &String {
|
||||
return &self.options.file_name;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct EditorOptions {
|
||||
/// Name of the file being editing
|
||||
pub(crate) file_name: String,
|
||||
/// Level of verboseness
|
||||
pub(crate) verboseness: usize,
|
||||
}
|
||||
|
||||
impl EditorOptions {
|
||||
/// Return default options
|
||||
pub(crate) fn new(file_name: String) -> EditorOptions {
|
||||
EditorOptions {file_name: file_name}
|
||||
EditorOptions {file_name: file_name, verboseness: 1}
|
||||
}
|
||||
}
|
165
src/main.rs
165
src/main.rs
|
@ -12,54 +12,127 @@ use std::io::{BufReader};
|
|||
use std::path::Path;
|
||||
use regex::Regex;
|
||||
|
||||
use std::convert::TryInto;
|
||||
|
||||
mod editor;
|
||||
mod piece_table;
|
||||
|
||||
// use termion::{color, cursor, clear};
|
||||
// use termion::event::*;
|
||||
use termion::async_stdin;
|
||||
use termion::input::{MouseTerminal};
|
||||
use termion::event::Key;
|
||||
use termion::input::TermRead;
|
||||
use termion::raw::IntoRawMode;
|
||||
use std::io::{self, Write};
|
||||
use std::io::{Write, stdout, stdin};
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
// TODO: Match command line options, different order, etc.
|
||||
let editor_options = process_args(&args);
|
||||
/*
|
||||
let file_name = match args.last() {
|
||||
Some(file_name) => file_name,
|
||||
None => panic!("Please specify a file to edit"),
|
||||
};
|
||||
*/
|
||||
let editor = initialize(editor_options);
|
||||
let editor = initialize(process_args(&args));
|
||||
let mut piece_table = editor.piece_table;
|
||||
|
||||
// Print the current file text onto screen
|
||||
let stdin = async_stdin();
|
||||
let mut stdout = MouseTerminal::from(io::stdout().into_raw_mode().unwrap());
|
||||
write!(stdout, "{}{}{}{}",
|
||||
let stdin = stdin();
|
||||
let mut stdout = stdout().into_raw_mode().unwrap();
|
||||
|
||||
let mut editor_x: usize = 1;
|
||||
let mut editor_y: usize = 0;
|
||||
write!(stdout,
|
||||
"{}{}",
|
||||
termion::clear::All,
|
||||
termion::cursor::Goto(1, 1),
|
||||
piece_table.text(),
|
||||
termion::cursor::Hide).unwrap();
|
||||
termion::cursor::Goto(1, 1)).unwrap();
|
||||
|
||||
for line in piece_table.text().lines() {
|
||||
editor_y += 1;
|
||||
write!(stdout,
|
||||
"{}{}",
|
||||
termion::cursor::Goto(editor_x.try_into().unwrap(), editor_y.try_into().unwrap()),
|
||||
line).unwrap();
|
||||
}
|
||||
stdout.flush().unwrap();
|
||||
|
||||
/*
|
||||
let mut buffer_index = piece_table.text_len();
|
||||
let mut cmd_index = 0;
|
||||
let mut cmd_piece_table = piece_table::PieceTable::new("".to_string());
|
||||
let mut mode = 1; // [0: insert, 1: visual, 2: command]
|
||||
for c in stdin.keys() {
|
||||
match c.unwrap() {
|
||||
Key::Esc => normal_mode(),
|
||||
Key::Char(':') => colon_mode(),
|
||||
Key::Char('i') => insert_mode(c.unwrap()),
|
||||
Key::Char('v') | Key::Char('V') => visual_mode(c.unwrap()),
|
||||
Key::Left | Key::Right => movement_horizontal(c.unwrap()),
|
||||
Key::Up | Key::Down => movement_vertical(c.unwrap()),
|
||||
Key::Char(c) => add_text_buffered(c),
|
||||
_ => unsupported(),
|
||||
write!(stdout,
|
||||
"{}{}",
|
||||
termion::cursor::Goto(1, 1),
|
||||
termion::clear::CurrentLine)
|
||||
.unwrap();
|
||||
|
||||
let i = c.unwrap();
|
||||
if i == Key::Esc {
|
||||
mode = 1
|
||||
} else if mode == 0 {
|
||||
// insert mode
|
||||
if i == Key::Backspace {
|
||||
if buffer_index != 0 {
|
||||
piece_table.delete_text(buffer_index - 1, buffer_index);
|
||||
buffer_index -= 1;
|
||||
|
||||
if editor_x == 1 {
|
||||
write!(stdout,
|
||||
"{}",
|
||||
termion::cursor::Up(1)).unwrap();
|
||||
// TODO: how to go to right x value?
|
||||
} else {
|
||||
write!(stdout, "{}", termion::cursor::Up(1)).unwrap();
|
||||
}
|
||||
}
|
||||
} else if i == Key::Up {
|
||||
|
||||
} else if i == Key::Right {
|
||||
|
||||
} else if i == Key::Down {
|
||||
|
||||
} else if i == Key::Left {
|
||||
|
||||
}
|
||||
} else if mode == 1 {
|
||||
// visual mode
|
||||
if i == Key::Char('i') {
|
||||
mode = 0;
|
||||
} else if i == Key::Char(':') {
|
||||
mode = 2;
|
||||
// TODO: Go to bottom line thing
|
||||
} else if i == Key::Up || i == Key::Char('k') {
|
||||
if let Ok(prev_line) = piece_table.line(editor_y - 2) {
|
||||
let prev_line_len = prev_line.len();
|
||||
buffer_index -= editor_x; // Including the new line
|
||||
if prev_line_len < editor_x - 1 {
|
||||
editor_x = prev_line_len + 1
|
||||
}
|
||||
buffer_index -= prev_line_len - (editor_x - 1);
|
||||
editor_y -= 1;
|
||||
write!(stdout, "{}", termion::cursor::Goto(editor_x.try_into().unwrap(), editor_y.try_into().unwrap())).unwrap()
|
||||
}
|
||||
// TODO: FIXME
|
||||
} else if i == Key::Right || i == Key::Char('l') {
|
||||
if let Ok(curr_line) = piece_table.line(editor_y - 1) {
|
||||
if curr_line.len() < editor_x - 1 {
|
||||
editor_x -= 1;
|
||||
write!(stdout, "{}", termion::cursor::Right(1)).unwrap();
|
||||
buffer_index += 1;
|
||||
}
|
||||
}
|
||||
} else if i == Key::Down || i == Key::Char('j') {
|
||||
write!(stdout, "{}", termion::cursor::Down(1)).unwrap();
|
||||
// TODO: FIXME
|
||||
} else if i == Key::Left || i == Key::Char('h') {
|
||||
if editor_y != 1 {
|
||||
write!(stdout, "{}", termion::cursor::Left(1)).unwrap();
|
||||
buffer_index -= 1;
|
||||
}
|
||||
}
|
||||
} else if mode == 2 {
|
||||
// command mode
|
||||
cmd_piece_table.add_text("q".to_string(), cmd_index);
|
||||
cmd_index += 1;
|
||||
}
|
||||
stdout.flush().unwrap();
|
||||
}
|
||||
*/
|
||||
|
||||
write!(stdout, "{}", termion::cursor::Show).unwrap();
|
||||
}
|
||||
|
||||
|
@ -81,16 +154,13 @@ fn process_args(args: &Vec<String>) -> editor::EditorOptions {
|
|||
file_name = file_names.first().unwrap().to_string();
|
||||
} else {
|
||||
println!("{:?}", file_names);
|
||||
panic!("Must specify only one filed to edit"); // Maybe change this to edit multiple files later on
|
||||
panic!("Must specify a single file to edit"); // Maybe change this to edit multiple files later on
|
||||
}
|
||||
let mut editor_options = editor::EditorOptions::new(file_name);
|
||||
for option in flags {
|
||||
/*
|
||||
Example:
|
||||
if (option == "-v") {
|
||||
editor_options.verbose = true;
|
||||
if option == "-v" {
|
||||
editor_options.verboseness += 1;
|
||||
}
|
||||
*/
|
||||
}
|
||||
editor_options
|
||||
}
|
||||
|
@ -112,18 +182,10 @@ fn initialize(editor_options: editor::EditorOptions) -> editor::Editor {
|
|||
// Read until viewport is filled
|
||||
// For now, only read 2 lines
|
||||
let mut initial_text =String::new();
|
||||
let eof_reached = read_lines(&mut reader, 2, &mut initial_text);
|
||||
let text_len = initial_text.len();
|
||||
let mut original_buffer = piece_table::Buffer::new();
|
||||
original_buffer.update_add(initial_text);
|
||||
let mut piece_table = piece_table::PieceTable::new(original_buffer);
|
||||
let mut first_entry = piece_table::TableEntry::new(false, 0, text_len);
|
||||
if text_len == 0 {
|
||||
first_entry.active = false;
|
||||
}
|
||||
piece_table.push(first_entry);
|
||||
let eof_reached = read_lines(&mut reader, 1000000000000000, &mut initial_text); // TODO: FIX ME
|
||||
// TODO: Add this to initialization of piece table
|
||||
|
||||
editor::Editor::new(piece_table, reader, eof_reached, editor_options)
|
||||
editor::Editor::new(piece_table::PieceTable::new(initial_text), reader, eof_reached, editor_options)
|
||||
}
|
||||
|
||||
/// Read `num_lines` from `reader`, append to str, and returns whether EOF reached
|
||||
|
@ -141,3 +203,16 @@ fn read_lines(reader: &mut BufReader<File>, num_lines: usize, str: &mut String)
|
|||
}
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
/*
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn init() {
|
||||
let args: Vec<String> = vec!["unittests/1.in".to_string()];
|
||||
let editor = initialize(process_args(&args));
|
||||
}
|
||||
*/
|
||||
}
|
|
@ -1,151 +1,191 @@
|
|||
// use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
#[derive(Debug)]
|
||||
/// The main structure for storing text
|
||||
pub(crate) struct PieceTable {
|
||||
/// The main table, contains `TableEntry`'s
|
||||
table: Vec<TableEntry>,
|
||||
/// Original buffer
|
||||
pub(crate) original_buffer: Buffer,
|
||||
pub(crate) original_buffer: String,
|
||||
/// Add buffer
|
||||
add_buffer: Buffer,
|
||||
add_buffer: String,
|
||||
/// All active text. Only to be used when `text_up_to_date == true`
|
||||
text: String,
|
||||
/// Length of text if it was up to date (`text` may not not be up to date)
|
||||
text_len: usize,
|
||||
/// Whether `text` is up to date
|
||||
text_up_to_date: bool,
|
||||
/// List of actions, which are lists of table entries
|
||||
///
|
||||
/// **NOTE**: Vectors stored are copies of the original
|
||||
actions: Vec<Vec<TableEntry>>,
|
||||
/// List of actions, which are lists of table entries' indices
|
||||
actions: Vec<Vec<usize>>,
|
||||
/// Where in `self.actions` we are currently at
|
||||
///
|
||||
/// **NOTE**: A value of 0 means no actions have been taken.
|
||||
/// A value of 1 means 1 action has been taken.
|
||||
/// **NOTE**: A value of 0 means no actions have been taken
|
||||
actions_index: usize,
|
||||
}
|
||||
|
||||
impl PieceTable {
|
||||
/// Initializes a piece table with the original buffer set
|
||||
pub(crate) fn new(original_buffer: Buffer) -> PieceTable {
|
||||
PieceTable {table: Vec::new(), original_buffer: original_buffer, add_buffer: Buffer::new(), text: String::new(), text_up_to_date: true, actions: Vec::new(), actions_index: 0}
|
||||
pub(crate) fn new(initial_text: String) -> PieceTable {
|
||||
let text_len = initial_text.len();
|
||||
let text = initial_text.clone();
|
||||
let table = if text_len == 0 {Vec::new()} else {vec![TableEntry::new(false, 0, text_len)]};
|
||||
PieceTable {
|
||||
table: table,
|
||||
original_buffer: initial_text,
|
||||
add_buffer: String::new(),
|
||||
text: text,
|
||||
text_len: text_len,
|
||||
text_up_to_date: true,
|
||||
actions: Vec::new(),
|
||||
actions_index: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Add text at a certain index
|
||||
pub(crate) fn add_text(&mut self, text: String, cursor: usize) {
|
||||
let text_len = self.text_len();
|
||||
let add_buffer_len = self.add_buffer.text().len();
|
||||
if cursor > text_len {
|
||||
panic!("cursor ({}) is a greater value than text len ({})", cursor, text_len);
|
||||
} else if cursor == 0 {
|
||||
let new_table_entry = TableEntry::new(true, add_buffer_len, add_buffer_len + text.len());
|
||||
self.add_action(vec![new_table_entry]);
|
||||
self.table.insert(0, new_table_entry);
|
||||
} else if cursor == text_len {
|
||||
let new_table_entry = TableEntry::new(true, add_buffer_len, add_buffer_len + text.len());
|
||||
self.add_action(vec![new_table_entry]);
|
||||
self.table.push(new_table_entry);
|
||||
let text_len = text.len();
|
||||
let add_buffer_len = self.add_buffer.len();
|
||||
if cursor > self.text_len {
|
||||
panic!("cursor ({}) is a greater value than text len ({})", cursor, self.text_len);
|
||||
}
|
||||
let mut curr_pos = 0;
|
||||
let mut table_entry_index = 0;
|
||||
let mut action: Vec<usize> = Vec::new();
|
||||
let mut add_table: Vec<TableEntry> = Vec::with_capacity(3);
|
||||
let mut add_table_indices: Vec<usize> = Vec::with_capacity(3);
|
||||
if cursor == 0 {
|
||||
self.table.insert(0, TableEntry::new(true, add_buffer_len, add_buffer_len + text.len()));
|
||||
action.push(0);
|
||||
} else if cursor == self.text_len {
|
||||
self.table.push(TableEntry::new(true, add_buffer_len, add_buffer_len + text.len()));
|
||||
action.push(self.table.len());
|
||||
} else {
|
||||
let mut table_entry_count = 0;
|
||||
let mut curr_pos = 0;
|
||||
for table_entry in &mut self.table {
|
||||
for table_entry in self.table.iter_mut() {
|
||||
if table_entry.active {
|
||||
let len = table_entry.end_index - table_entry.start_index;
|
||||
if curr_pos + len > cursor {
|
||||
let last_table_entry_length = len - (cursor - curr_pos);
|
||||
let old_table_entry_end_index = table_entry.end_index;
|
||||
|
||||
table_entry.end_index = table_entry.end_index - last_table_entry_length;
|
||||
let last_table_entry = TableEntry::new(table_entry.is_add_buffer, table_entry.end_index, old_table_entry_end_index);
|
||||
self.table.insert(table_entry_count + 1, last_table_entry);
|
||||
|
||||
let middle_table_entry = TableEntry::new(true, add_buffer_len, add_buffer_len + text.len());
|
||||
self.add_action(vec![middle_table_entry]);
|
||||
self.table.insert(table_entry_count + 1, middle_table_entry);
|
||||
if curr_pos == cursor {
|
||||
add_table.push(TableEntry::new(true, add_buffer_len, add_buffer_len + text.len()));
|
||||
add_table_indices.push(table_entry_index);
|
||||
break
|
||||
} else if curr_pos == cursor {
|
||||
let new_table_entry = TableEntry::new(true, add_buffer_len, add_buffer_len + text.len());
|
||||
self.add_action(vec![new_table_entry]);
|
||||
self.table.insert(table_entry_count, new_table_entry);
|
||||
} else if curr_pos + len > cursor {
|
||||
// Split into 2 parts and disable original [ab] + [c] -> [a][c][b]
|
||||
let split_point = cursor - curr_pos;
|
||||
|
||||
table_entry.active = false;
|
||||
action.push(table_entry_index);
|
||||
|
||||
add_table.push(TableEntry::new(table_entry.is_add_buffer, table_entry.start_index, table_entry.start_index + split_point));
|
||||
action.push(table_entry_index + 1);
|
||||
add_table.push(TableEntry::new(true, add_buffer_len, add_buffer_len + text.len()));
|
||||
action.push(table_entry_index + 2);
|
||||
add_table.push(TableEntry::new(table_entry.is_add_buffer, table_entry.start_index + split_point, table_entry.end_index));
|
||||
action.push(table_entry_index + 3);
|
||||
|
||||
add_table_indices.push(table_entry_index);
|
||||
add_table_indices.push(table_entry_index + 1);
|
||||
add_table_indices.push(table_entry_index + 2);
|
||||
break
|
||||
}
|
||||
curr_pos += len;
|
||||
}
|
||||
table_entry_count += 1;
|
||||
table_entry_index += 1;
|
||||
}
|
||||
|
||||
table_entry_index = 0;
|
||||
for table_entry in add_table {
|
||||
self.table.insert(*add_table_indices.get(table_entry_index).unwrap(), table_entry);
|
||||
table_entry_index += 1;
|
||||
}
|
||||
}
|
||||
self.add_buffer.update_add(text);
|
||||
|
||||
self.add_buffer.push_str(&text);
|
||||
self.add_action(action);
|
||||
self.text_len += text_len;
|
||||
self.text_up_to_date = false;
|
||||
}
|
||||
|
||||
/// Delete text from `start` to `end`
|
||||
fn delete_text(&mut self, start: usize, end: usize) {
|
||||
let text_len = self.text_len();
|
||||
if start >= end || end == 0 || end > text_len {
|
||||
panic!("Can't delete from start ({}) to end ({}) of text size {}", start, end, self.text_len());
|
||||
pub(crate) fn delete_text(&mut self, start: usize, end: usize) {
|
||||
if start >= end || end == 0 || end > self.text_len {
|
||||
panic!("Can't delete from start ({}) to end ({}) of text size {}", start, end, self.text_len);
|
||||
}
|
||||
let mut curr_pos = 0;
|
||||
let mut table_entry_count = 0;
|
||||
let mut action: Vec<TableEntry> = Vec::new();
|
||||
let mut table_entry_index = 0;
|
||||
let mut action: Vec<usize> = Vec::new();
|
||||
let mut add_table: Vec<TableEntry> = Vec::with_capacity(4);
|
||||
let mut add_table_indices: Vec<usize> = Vec::with_capacity(4);
|
||||
|
||||
let mut temp_table_entry = TableEntry::new(true, 0, 0);
|
||||
let mut temp_table_set = false;
|
||||
let mut temp_table_index = 0;
|
||||
for table_entry in self.table.iter_mut() {
|
||||
if curr_pos == end {
|
||||
break
|
||||
}
|
||||
if table_entry.active {
|
||||
let len = table_entry.end_index - table_entry.start_index;
|
||||
if curr_pos <= start && curr_pos + len > start && curr_pos + len <= end {
|
||||
// At table entry to start at and split, but possibly continue
|
||||
if start <= curr_pos && end >= curr_pos + len {
|
||||
// At table entry to continue/end at
|
||||
// OR start & end is this exact table entry
|
||||
table_entry.active = false;
|
||||
action.push(table_entry_index + add_table.len());
|
||||
} else if start > curr_pos && start < curr_pos + len && end >= curr_pos + len {
|
||||
// At table entry to start at (split table entry in two)
|
||||
// Only occurs once, and will be the first
|
||||
let split_point = start - curr_pos;
|
||||
temp_table_entry = TableEntry::new(table_entry.is_add_buffer, table_entry.start_index, table_entry.start_index + split_point);
|
||||
table_entry.start_index = table_entry.start_index + split_point;
|
||||
|
||||
table_entry.active = false;
|
||||
action.push(*table_entry);
|
||||
action.push(table_entry_index);
|
||||
|
||||
temp_table_index = table_entry_count;
|
||||
temp_table_set = true;
|
||||
} else if curr_pos >= start && curr_pos + len <= end {
|
||||
// Start is this/before this cell & end is this/after this cell
|
||||
table_entry.active = false;
|
||||
action.push(*table_entry);
|
||||
} else if curr_pos >= start && curr_pos + len > end {
|
||||
// At the table entry to end at and split
|
||||
add_table.push(TableEntry::new(table_entry.is_add_buffer, table_entry.start_index, table_entry.start_index + split_point));
|
||||
add_table_indices.push(table_entry_index + 1);
|
||||
action.push(table_entry_index + 1);
|
||||
} else if start <= curr_pos && end > curr_pos && end < curr_pos + len {
|
||||
// At table entry to end at (split table entry in two)
|
||||
let split_point = end - curr_pos;
|
||||
let mut temp_table_entry = TableEntry::new(table_entry.is_add_buffer, table_entry.start_index, table_entry.start_index + split_point);
|
||||
temp_table_entry.active = false;
|
||||
table_entry.start_index = table_entry.start_index + split_point;
|
||||
action.push(temp_table_entry);
|
||||
self.table.insert(table_entry_count, temp_table_entry);
|
||||
break
|
||||
} else if curr_pos < start && curr_pos + len > start && curr_pos + len > end {
|
||||
// At table entry to split into 3 [abc] -> [a](b)[c]
|
||||
let split_point_first = start - curr_pos;
|
||||
let split_point_second = end - curr_pos;
|
||||
let old_end_index = table_entry.end_index;
|
||||
|
||||
table_entry.end_index = split_point_first;
|
||||
let mut middle_table_entry = TableEntry::new(table_entry.is_add_buffer, table_entry.start_index + split_point_first, table_entry.start_index + split_point_second);
|
||||
middle_table_entry.active = false;
|
||||
action.push(middle_table_entry);
|
||||
let end_table_entry = TableEntry::new(table_entry.is_add_buffer, table_entry.start_index + split_point_second, old_end_index);
|
||||
self.table.insert(table_entry_count + 1, middle_table_entry);
|
||||
self.table.insert(table_entry_count + 2, end_table_entry);
|
||||
table_entry.active = false;
|
||||
action.push(table_entry_index + add_table.len());
|
||||
|
||||
add_table.push(TableEntry::new(table_entry.is_add_buffer, table_entry.start_index + split_point, table_entry.end_index));
|
||||
add_table_indices.push(table_entry_index + add_table.len());
|
||||
action.push(table_entry_index + add_table.len());
|
||||
break
|
||||
} else if start > curr_pos && end < curr_pos + len {
|
||||
// At table entry to split into 3 [abc] -> [a](b)[c]
|
||||
// Only occurs once, and will be the first/last
|
||||
let first_split = start - curr_pos;
|
||||
let second_split = end - curr_pos;
|
||||
|
||||
table_entry.active = false;
|
||||
action.push(table_entry_index);
|
||||
|
||||
add_table.push(TableEntry::new(table_entry.is_add_buffer, table_entry.start_index, table_entry.start_index + first_split));
|
||||
add_table_indices.push(table_entry_index + 1);
|
||||
action.push(table_entry_index + 1);
|
||||
|
||||
add_table.push(TableEntry::new(table_entry.is_add_buffer, table_entry.start_index + second_split, table_entry.end_index));
|
||||
add_table_indices.push(table_entry_index + 2);
|
||||
action.push(table_entry_index + 2);
|
||||
break
|
||||
}
|
||||
curr_pos += len;
|
||||
}
|
||||
table_entry_count += 1;
|
||||
table_entry_index += 1;
|
||||
}
|
||||
if temp_table_set {
|
||||
self.table.insert(temp_table_index, temp_table_entry);
|
||||
|
||||
table_entry_index = 0;
|
||||
for table_entry in add_table {
|
||||
self.table.insert(*add_table_indices.get(table_entry_index).unwrap(), table_entry);
|
||||
table_entry_index += 1;
|
||||
}
|
||||
|
||||
self.add_action(action);
|
||||
self.text_len -= end - start;
|
||||
self.text_up_to_date = false;
|
||||
}
|
||||
|
||||
/// Add a table entry to actions
|
||||
fn add_action(&mut self, action: Vec<TableEntry>) {
|
||||
fn add_action(&mut self, action: Vec<usize>) {
|
||||
// Remove actions after current index
|
||||
// TODO: Remove all unecessary TableEntry's
|
||||
self.actions = self.actions[..self.actions_index].to_vec();
|
||||
self.actions.push(action);
|
||||
self.actions_index = self.actions.len();
|
||||
|
@ -156,22 +196,17 @@ impl PieceTable {
|
|||
if self.actions.is_empty() {
|
||||
panic!("Unable to undo");
|
||||
}
|
||||
// TODO: I'm not sure how computationally expensive this is, but it probably could be improved
|
||||
match self.actions.get(self.actions_index - 1) {
|
||||
Some(action) => {
|
||||
for table_entry_copy in action {
|
||||
for table_entry in self.table.iter_mut() {
|
||||
if table_entry.is_add_buffer == table_entry_copy.is_add_buffer
|
||||
&& table_entry.start_index == table_entry_copy.start_index
|
||||
&& table_entry.end_index == table_entry_copy.end_index {
|
||||
table_entry.switch();
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for index in self.actions.get(self.actions_index - 1).unwrap() {
|
||||
let table_entry = self.table.get_mut(*index).unwrap();
|
||||
table_entry.switch();
|
||||
if table_entry.active {
|
||||
self.text_len += table_entry.end_index - table_entry.start_index;
|
||||
} else {
|
||||
self.text_len -= table_entry.end_index - table_entry.start_index;
|
||||
}
|
||||
None => panic!("Unable to get last action"),
|
||||
};
|
||||
}
|
||||
|
||||
self.text_up_to_date = false;
|
||||
self.actions_index -= 1;
|
||||
}
|
||||
|
@ -181,71 +216,54 @@ impl PieceTable {
|
|||
if self.actions.is_empty() || self.actions.len() <= self.actions_index {
|
||||
panic!("Unable to redo");
|
||||
}
|
||||
match self.actions.get(self.actions_index) {
|
||||
Some(action) => {
|
||||
for table_entry_copy in action {
|
||||
for table_entry in self.table.iter_mut() {
|
||||
if table_entry.is_add_buffer == table_entry_copy.is_add_buffer
|
||||
&& table_entry.start_index == table_entry_copy.start_index
|
||||
&& table_entry.end_index == table_entry_copy.end_index {
|
||||
table_entry.switch();
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
for index in self.actions.get(self.actions_index).unwrap() {
|
||||
let table_entry = self.table.get_mut(*index).unwrap();
|
||||
table_entry.switch();
|
||||
if table_entry.active {
|
||||
self.text_len += table_entry.end_index - table_entry.start_index;
|
||||
} else {
|
||||
self.text_len -= table_entry.end_index - table_entry.start_index;
|
||||
}
|
||||
None => panic!("Unable to get next action"),
|
||||
};
|
||||
}
|
||||
self.text_up_to_date = false;
|
||||
self.actions_index += 1;
|
||||
}
|
||||
|
||||
/// Returns the text represented by a table entry.
|
||||
///
|
||||
/// Assumes buffer is up to date
|
||||
/// Returns the text represented by a table entry
|
||||
fn table_entry_text(&self, table_entry: &TableEntry) -> &str {
|
||||
let buffer = if table_entry.is_add_buffer {&self.add_buffer} else {&self.original_buffer};
|
||||
assert!(buffer.text_up_to_date);
|
||||
match buffer.text().get(table_entry.start_index..table_entry.end_index) {
|
||||
Some(text) => text,
|
||||
None => panic!("Unable to get {}[{}..{}]", buffer.text(), table_entry.start_index, table_entry.end_index),
|
||||
}
|
||||
buffer.get(table_entry.start_index..table_entry.end_index).unwrap()
|
||||
}
|
||||
|
||||
/// Returns all visible text.
|
||||
/// Returns length of text
|
||||
pub(crate) fn text_len(&self) -> usize {
|
||||
self.text_len
|
||||
}
|
||||
|
||||
/// Returns all visible text
|
||||
///
|
||||
/// If you want to get the length of the text, use `text_len(&self)` instead
|
||||
pub(crate) fn text(&mut self) -> &str {
|
||||
let a = self.original_buffer.update_text();
|
||||
let b = self.add_buffer.update_text();
|
||||
if self.text_up_to_date && !a && !b {
|
||||
return &self.text;
|
||||
self.update_text();
|
||||
&self.text
|
||||
}
|
||||
|
||||
/// Updates all text for the piece table
|
||||
fn update_text(&mut self) {
|
||||
if self.text_up_to_date {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Be more efficient about doing this
|
||||
let mut text = String::new();
|
||||
for table_entry in &self.table {
|
||||
if table_entry.active {
|
||||
text.push_str(self.table_entry_text(table_entry));
|
||||
}
|
||||
}
|
||||
|
||||
// assert_eq!(text.len(), self.text_len); // TODO: REMOVE ME LATER
|
||||
self.text = text;
|
||||
self.text_up_to_date = true;
|
||||
&self.text
|
||||
}
|
||||
|
||||
/// A slightly more efficient way of calculating the length of `self.text().len()`
|
||||
fn text_len(&self) -> usize {
|
||||
if self.text_up_to_date {
|
||||
return self.text.len();
|
||||
}
|
||||
|
||||
// TODO: Be more efficient about doing this
|
||||
let mut len = 0;
|
||||
for table_entry in &self.table {
|
||||
if table_entry.active {
|
||||
len += table_entry.end_index - table_entry.start_index;
|
||||
}
|
||||
}
|
||||
len
|
||||
}
|
||||
|
||||
/// Insert a table entry to a specific index
|
||||
|
@ -255,16 +273,29 @@ impl PieceTable {
|
|||
}
|
||||
|
||||
/// Add a table entry to the end of the table
|
||||
pub(crate) fn push(&mut self, table_entry: TableEntry) {
|
||||
fn push(&mut self, table_entry: TableEntry) {
|
||||
self.table.push(table_entry);
|
||||
self.text_up_to_date = false;
|
||||
}
|
||||
|
||||
/// Return the index-th line
|
||||
/// TODO: Remove this and do something better regarding lines
|
||||
pub(crate) fn line(&mut self, index: usize) -> Result<&str, &str> {
|
||||
let mut count: usize = 0;
|
||||
for line in self.text().lines() {
|
||||
if count == index {
|
||||
return Ok(line)
|
||||
}
|
||||
count += 1
|
||||
}
|
||||
Err("Invalid line index")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)] // Needed for PieceTable.actions
|
||||
#[derive(Debug)]
|
||||
/// An entry in PieceTable's table
|
||||
pub(crate) struct TableEntry {
|
||||
struct TableEntry {
|
||||
/// Whether this table entry points to the add buffer
|
||||
is_add_buffer: bool,
|
||||
/// Start index
|
||||
|
@ -272,13 +303,18 @@ pub(crate) struct TableEntry {
|
|||
/// End index
|
||||
end_index: usize,
|
||||
/// Whether this table is visible
|
||||
pub(crate) active: bool,
|
||||
active: bool,
|
||||
}
|
||||
|
||||
impl TableEntry {
|
||||
/// Initalize a table entry
|
||||
pub(crate) fn new(is_add_buffer: bool, start_index: usize, end_index: usize) -> TableEntry {
|
||||
TableEntry {is_add_buffer: is_add_buffer, start_index: start_index, end_index: end_index, active: true}
|
||||
TableEntry {
|
||||
is_add_buffer: is_add_buffer,
|
||||
start_index: start_index,
|
||||
end_index: end_index,
|
||||
active: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Change from active to deactivated and visa versa
|
||||
|
@ -287,58 +323,124 @@ impl TableEntry {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Immutable text (abstracted)
|
||||
pub(crate) struct Buffer {
|
||||
/// Text contained in this buffer. Only use when `text_up_to_date == true`
|
||||
text: String,
|
||||
/// Whether `text` is up to date
|
||||
text_up_to_date: bool,
|
||||
/// Text pieces making up `text`
|
||||
text_pieces: Vec<String>,
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
impl Buffer {
|
||||
/// Initializes the buffer with an empty string
|
||||
pub(crate) fn new() -> Buffer {
|
||||
Buffer {text: String::from(""), text_up_to_date: true, text_pieces: Vec::new()}
|
||||
#[test]
|
||||
fn add_text() {
|
||||
let mut piece_table = PieceTable::new("a".to_string());
|
||||
let mut want_str = "a";
|
||||
assert_eq!(piece_table.text_len, want_str.len());
|
||||
assert_eq!(piece_table.text(), want_str);
|
||||
|
||||
piece_table.add_text(" b".to_string(), 1);
|
||||
want_str = "a b";
|
||||
assert_eq!(piece_table.text_len, want_str.len());
|
||||
assert_eq!(piece_table.text(), want_str);
|
||||
|
||||
piece_table.add_text("c ".to_string(), 2);
|
||||
want_str = "a c b";
|
||||
assert_eq!(piece_table.text_len, want_str.len());
|
||||
assert_eq!(piece_table.text(), want_str);
|
||||
|
||||
piece_table.add_text("d ".to_string(), 0);
|
||||
want_str = "d a c b";
|
||||
assert_eq!(piece_table.text_len, want_str.len());
|
||||
assert_eq!(piece_table.text(), want_str);
|
||||
}
|
||||
|
||||
fn text(&self) -> &str {
|
||||
&self.text
|
||||
#[test]
|
||||
fn delete_text() {
|
||||
let mut piece_table = PieceTable::new("abc".to_string());
|
||||
piece_table.delete_text(0, 1);
|
||||
let mut want_str = "bc";
|
||||
assert_eq!(piece_table.text_len, want_str.len());
|
||||
assert_eq!(piece_table.text(), want_str);
|
||||
|
||||
piece_table = PieceTable::new("".to_string());
|
||||
piece_table.add_text("abc".to_string(), 0);
|
||||
piece_table.delete_text(1, 2);
|
||||
want_str = "ac";
|
||||
assert_eq!(piece_table.text_len, want_str.len());
|
||||
assert_eq!(piece_table.text(), want_str);
|
||||
|
||||
piece_table = PieceTable::new("abc".to_string());
|
||||
piece_table.delete_text(2, 3);
|
||||
want_str = "ab";
|
||||
assert_eq!(piece_table.text_len, want_str.len());
|
||||
assert_eq!(piece_table.text(), want_str);
|
||||
|
||||
piece_table = PieceTable::new("abc".to_string());
|
||||
piece_table.delete_text(2, 3);
|
||||
want_str = "ab";
|
||||
assert_eq!(piece_table.text_len, want_str.len());
|
||||
assert_eq!(piece_table.text(), want_str);
|
||||
|
||||
piece_table = PieceTable::new("ab".to_string());
|
||||
piece_table.add_text("cd".to_string(), 2);
|
||||
piece_table.delete_text(2, 3);
|
||||
want_str = "abd";
|
||||
assert_eq!(piece_table.text_len, want_str.len());
|
||||
assert_eq!(piece_table.text(), want_str);
|
||||
|
||||
piece_table = PieceTable::new("ab".to_string());
|
||||
piece_table.add_text("cd".to_string(), 2);
|
||||
piece_table.add_text("ef".to_string(), 4);
|
||||
piece_table.delete_text(1, 5);
|
||||
want_str = "af";
|
||||
assert_eq!(piece_table.text_len, want_str.len());
|
||||
assert_eq!(piece_table.text(), want_str);
|
||||
|
||||
piece_table = PieceTable::new("ab".to_string());
|
||||
piece_table.add_text("cd".to_string(), 2);
|
||||
piece_table.add_text("ef".to_string(), 4);
|
||||
piece_table.delete_text(1, 6);
|
||||
want_str = "a";
|
||||
assert_eq!(piece_table.text_len, want_str.len());
|
||||
assert_eq!(piece_table.text(), want_str);
|
||||
|
||||
piece_table = PieceTable::new("ab".to_string());
|
||||
piece_table.add_text("cd".to_string(), 2);
|
||||
piece_table.add_text("ef".to_string(), 4);
|
||||
piece_table.delete_text(0, 6);
|
||||
want_str = "";
|
||||
assert_eq!(piece_table.text_len, want_str.len());
|
||||
assert_eq!(piece_table.text(), want_str);
|
||||
}
|
||||
|
||||
/// Make `self.text` up to date.
|
||||
///
|
||||
/// Returns whether text was updated or not
|
||||
fn update_text(&mut self) -> bool {
|
||||
if self.text_up_to_date {
|
||||
return false;
|
||||
}
|
||||
// TODO: Change this to keep track of self.text_pieces_index to be more efficient
|
||||
// e.g. start at the last added index rather than creating from scratch every time
|
||||
self.text.clear();
|
||||
for s in &self.text_pieces {
|
||||
self.text.push_str(&s);
|
||||
}
|
||||
self.text_up_to_date = true;
|
||||
true
|
||||
}
|
||||
#[test]
|
||||
fn undo_redo() {
|
||||
let mut piece_table = PieceTable::new("abc".to_string());
|
||||
piece_table.delete_text(0, 1);
|
||||
let mut want_str = "abc";
|
||||
piece_table.undo();
|
||||
assert_eq!(piece_table.text_len, want_str.len());
|
||||
assert_eq!(piece_table.text(), want_str);
|
||||
|
||||
/// Add a text piece to text pieces
|
||||
fn add(&mut self, text: String) {
|
||||
self.text_pieces.push(text);
|
||||
self.text_up_to_date = false;
|
||||
}
|
||||
piece_table.redo();
|
||||
want_str = "bc";
|
||||
assert_eq!(piece_table.text_len, want_str.len());
|
||||
assert_eq!(piece_table.text(), want_str);
|
||||
|
||||
/// Add a text piece AND update `self.text`
|
||||
pub(crate) fn update_add(&mut self, text: String) {
|
||||
if self.text_up_to_date {
|
||||
self.text.push_str(text.as_str());
|
||||
self.text_pieces.push(text);
|
||||
} else {
|
||||
self.add(text);
|
||||
self.update_text();
|
||||
}
|
||||
piece_table = PieceTable::new("abc".to_string());
|
||||
piece_table.add_text("d".to_string(), 3); // "abcd"
|
||||
piece_table.delete_text(0, 2); // "cd"
|
||||
piece_table.undo();
|
||||
want_str = "abcd";
|
||||
assert_eq!(piece_table.text_len, want_str.len());
|
||||
assert_eq!(piece_table.text(), want_str);
|
||||
piece_table.undo();
|
||||
want_str = "abc";
|
||||
assert_eq!(piece_table.text_len, want_str.len());
|
||||
assert_eq!(piece_table.text(), want_str);
|
||||
piece_table.redo();
|
||||
want_str = "abcd";
|
||||
assert_eq!(piece_table.text_len, want_str.len());
|
||||
assert_eq!(piece_table.text(), want_str);
|
||||
piece_table.redo();
|
||||
want_str = "cd";
|
||||
assert_eq!(piece_table.text_len, want_str.len());
|
||||
assert_eq!(piece_table.text(), want_str);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue