Restructure structs

Start adding TTY support
This commit is contained in:
Andrew Dinh 2020-07-14 14:05:26 -07:00
parent 255d449e67
commit 939182c276
5 changed files with 588 additions and 393 deletions

91
Cargo.lock generated
View File

@ -1,5 +1,96 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "aho-corasick"
version = "0.7.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86"
dependencies = [
"memchr",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9f8082297d534141b30c8d39e9b1773713ab50fdbe4ff30f750d063b3bfd701"
[[package]]
name = "memchr"
version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
[[package]]
name = "numtoa"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
[[package]]
name = "redox_syscall"
version = "0.1.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
[[package]]
name = "redox_termios"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
dependencies = [
"redox_syscall",
]
[[package]]
name = "regex"
version = "1.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
"thread_local",
]
[[package]]
name = "regex-syntax"
version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8"
[[package]]
name = "termion"
version = "1.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c22cec9d8978d906be5ac94bceb5a010d885c626c4c8855721a4dbd20e3ac905"
dependencies = [
"libc",
"numtoa",
"redox_syscall",
"redox_termios",
]
[[package]]
name = "thread_local"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
dependencies = [
"lazy_static",
]
[[package]]
name = "via"
version = "0.1.0"
dependencies = [
"regex",
"termion",
]

View File

@ -7,3 +7,5 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
termion = "^1.5"
regex = "^1.3"

53
src/editor.rs Normal file
View File

@ -0,0 +1,53 @@
// mod piece_table;
use std::fs::File;
use std::io::{BufReader};
#[derive(Debug)]
/// The overarching editor class
pub(crate) struct Editor {
/// The piece table
pub(crate) piece_table: super::piece_table::PieceTable,
/// Reader of the file
reader: BufReader<File>,
/// Whether we have read all of `self.reader`
eof_reached: bool,
/// Configured options
options: EditorOptions,
}
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}
}
/// Read to end of the file and add it to `piece_table.original_buffer`
fn read_to_eof(&mut self) {
let mut final_str = String::new();
let mut temp_str = String::new();
loop {
// match self.reader.read_line(&mut temp_str) {
match std::io::BufRead::read_line(&mut self.reader, &mut temp_str) {
Ok(0) => break,
Ok(len) => len,
Err(e) => panic!("Error reading file: {:?}", e),
};
final_str.push_str(&temp_str);
temp_str.clear();
}
self.piece_table.original_buffer.update_add(final_str);
}
}
#[derive(Debug)]
pub(crate) struct EditorOptions {
pub(crate) file_name: String,
}
impl EditorOptions {
/// Return default options
pub(crate) fn new(file_name: String) -> EditorOptions {
EditorOptions {file_name: file_name}
}
}

View File

@ -2,434 +2,139 @@
// #![warn(unused_variables)]
// #![warn(unused_mut)]
extern crate termion;
extern crate regex;
use std::env;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::io::{BufReader};
// use std::io::prelude::*;
use std::path::Path;
use regex::Regex;
#[derive(Debug)]
/// The overarching editor class
struct Editor {
/// The piece table
piece_table: PieceTable,
/// Reader of the file
reader: BufReader<File>,
/// Whether we have read all of `self.reader`
eof_reached: bool,
}
mod editor;
mod piece_table;
impl Editor {
/// Initialize a new editor with a piece table and reader
fn new(piece_table: PieceTable, reader: BufReader<File>, eof_reached: bool) -> Editor {
Editor {piece_table: piece_table, reader: reader, eof_reached: eof_reached}
}
/// Read to end of the file and add it to `piece_table.original_buffer`
fn read_to_eof(&mut self) {
let mut final_str = String::new();
let mut temp_str = String::new();
loop {
match self.reader.read_line(&mut temp_str) {
Ok(0) => break,
Ok(len) => len,
Err(e) => panic!("Error reading file: {:?}", e),
};
final_str.push_str(&temp_str);
temp_str.clear();
}
self.piece_table.original_buffer.update_add(final_str);
}
}
#[derive(Debug)]
/// The main structure for storing text
struct PieceTable {
/// The main table, contains `TableEntry`'s
table: Vec<TableEntry>,
/// Original buffer
original_buffer: Buffer,
/// Add buffer
add_buffer: Buffer,
/// All active text. Only to be used when `text_up_to_date == true`
text: String,
/// 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>>,
/// 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.
actions_index: usize,
}
impl PieceTable {
/// Initializes a piece table with the original buffer set
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}
}
/// Add text at a certain index
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);
} else {
let mut table_entry_count = 0;
let mut curr_pos = 0;
for table_entry in &mut self.table {
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);
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);
break
}
curr_pos += len;
}
table_entry_count += 1;
}
}
self.add_buffer.update_add(text);
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());
}
let mut curr_pos = 0;
let mut table_entry_count = 0;
let mut action: Vec<TableEntry> = Vec::new();
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
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);
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
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);
break
}
curr_pos += len;
}
table_entry_count += 1;
}
if temp_table_set {
self.table.insert(temp_table_index, temp_table_entry);
}
self.add_action(action);
self.text_up_to_date = false;
}
/// Add a table entry to actions
fn add_action(&mut self, action: Vec<TableEntry>) {
// Remove actions after current index
self.actions = self.actions[..self.actions_index].to_vec();
self.actions.push(action);
self.actions_index = self.actions.len();
}
/// Undo an action. Errors if no actions to undo
fn undo(&mut self) {
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
}
}
}
}
None => panic!("Unable to get last action"),
};
self.text_up_to_date = false;
self.actions_index -= 1;
}
/// Redo an action. Errors if no actions to redo
fn redo(&mut self) {
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
}
}
}
}
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
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_eq!(buffer.text_up_to_date, true);
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),
}
}
/// Returns all visible text.
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;
}
// 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));
}
}
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
fn insert(&mut self, index: usize, table_entry: TableEntry) {
self.table.insert(index, table_entry);
self.text_up_to_date = false;
}
/// Add a table entry to the end of the table
fn push(&mut self, table_entry: TableEntry) {
self.table.push(table_entry);
self.text_up_to_date = false;
}
}
#[derive(Copy, Clone)] // Needed for PieceTable.actions
#[derive(Debug)]
/// An entry in PieceTable's table
struct TableEntry {
/// Whether this table entry points to the add buffer
is_add_buffer: bool,
/// Start index
start_index: usize,
/// End index
end_index: usize,
/// Whether this table is visible
active: bool,
}
impl TableEntry {
/// Initalize a table entry
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}
}
/// Change from active to deactivated and visa versa
fn switch(&mut self) {
self.active = !self.active;
}
}
#[derive(Debug)]
/// Immutable text (abstracted)
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>,
}
impl Buffer {
/// Initializes the buffer with an empty string
fn new() -> Buffer {
Buffer {text: String::from(""), text_up_to_date: true, text_pieces: Vec::new()}
}
fn text(&self) -> &str {
&self.text
}
/// 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
}
/// Add a text piece to text pieces
fn add(&mut self, text: String) {
self.text_pieces.push(text);
self.text_up_to_date = false;
}
/// Add a text piece AND update `self.text`
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();
}
}
}
// use termion::{color, cursor, clear};
// use termion::event::*;
use termion::async_stdin;
use termion::input::{MouseTerminal};
use termion::raw::IntoRawMode;
use std::io::{self, Write};
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(&file_name);
*/
let editor = initialize(editor_options);
let mut piece_table = editor.piece_table;
println!("Org: {}", piece_table.text());
piece_table.delete_text(1, 2);
println!("New: {}", piece_table.text());
// Print the current file text onto screen
let stdin = async_stdin();
let mut stdout = MouseTerminal::from(io::stdout().into_raw_mode().unwrap());
write!(stdout, "{}{}{}{}",
termion::clear::All,
termion::cursor::Goto(1, 1),
piece_table.text(),
termion::cursor::Hide).unwrap();
stdout.flush().unwrap();
/*
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(),
}
stdout.flush().unwrap();
}
write!(stdout, "{}", termion::cursor::Show).unwrap();
*/
}
fn initialize(file_name: &String) -> Editor {
// TODO: Create file if doesn't exist / detect if is folder, etc
// EDIT: Might not want to create file, but instead write to memory then at the end then write to file
let file = File::open(file_name).expect("Failed to open file");
/// Process command line options and return EditorOptions
fn process_args(args: &Vec<String>) -> editor::EditorOptions {
let mut flags = Vec::new();
let mut file_names = Vec::new();
let file_name: String;
let flags_regex = Regex::new(r"--?\w+").unwrap();
let default_files = vec!["target/debug/via", "via"];
for arg in args {
if flags_regex.is_match(&arg) {
flags.push(arg);
} else if !default_files.contains(&arg.as_str()) {
file_names.push(arg);
}
}
if file_names.len() == 1 {
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
}
let mut editor_options = editor::EditorOptions::new(file_name);
for option in flags {
/*
Example:
if (option == "-v") {
editor_options.verbose = true;
}
*/
}
editor_options
}
fn initialize(editor_options: editor::EditorOptions) -> editor::Editor {
// TODO: Might not want to create file, but instead write to memory then at the end then write to file
let file_name = &editor_options.file_name;
let file_path = Path::new(&file_name);
let file: File;
if !file_path.is_file() {
panic!("{} is not a file", file_name);
} else if file_path.exists() {
file = File::open(file_name).expect("Failed to open file");
} else {
File::create(file_path).expect("Unable to create file");
file = File::open(file_name).expect("Failed to open file");
}
let mut reader = BufReader::new(file);
// Read until viewport is reached
// 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 = Buffer::new();
let mut original_buffer = piece_table::Buffer::new();
original_buffer.update_add(initial_text);
let mut piece_table = PieceTable::new(original_buffer);
let mut first_entry = TableEntry::new(false, 0, text_len);
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);
Editor::new(piece_table, reader, eof_reached)
editor::Editor::new(piece_table, reader, eof_reached, editor_options)
}
/// Read `num_lines` from `reader`, append to str, and returns whether EOF reached
fn read_lines(reader: &mut BufReader<File>, num_lines: usize, str: &mut String) -> bool {
let mut temp_str = String::new();
for _ in 0..num_lines {
match reader.read_line(&mut temp_str) {
// match reader.read_line(&mut temp_str) {
match std::io::BufRead::read_line(reader, &mut temp_str) {
Ok(0) => return true,
Ok(len) => len,
Err(e) => panic!("Error reading file: {:?}", e),
Err(e) => panic!("Error reading file: {}", e),
};
str.push_str(&temp_str);
temp_str.clear();

344
src/piece_table.rs Normal file
View File

@ -0,0 +1,344 @@
#[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,
/// Add buffer
add_buffer: Buffer,
/// All active text. Only to be used when `text_up_to_date == true`
text: String,
/// 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>>,
/// 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.
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}
}
/// 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);
} else {
let mut table_entry_count = 0;
let mut curr_pos = 0;
for table_entry in &mut self.table {
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);
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);
break
}
curr_pos += len;
}
table_entry_count += 1;
}
}
self.add_buffer.update_add(text);
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());
}
let mut curr_pos = 0;
let mut table_entry_count = 0;
let mut action: Vec<TableEntry> = Vec::new();
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
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);
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
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);
break
}
curr_pos += len;
}
table_entry_count += 1;
}
if temp_table_set {
self.table.insert(temp_table_index, temp_table_entry);
}
self.add_action(action);
self.text_up_to_date = false;
}
/// Add a table entry to actions
fn add_action(&mut self, action: Vec<TableEntry>) {
// Remove actions after current index
self.actions = self.actions[..self.actions_index].to_vec();
self.actions.push(action);
self.actions_index = self.actions.len();
}
/// Undo an action. Errors if no actions to undo
fn undo(&mut self) {
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
}
}
}
}
None => panic!("Unable to get last action"),
};
self.text_up_to_date = false;
self.actions_index -= 1;
}
/// Redo an action. Errors if no actions to redo
fn redo(&mut self) {
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
}
}
}
}
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
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),
}
}
/// Returns all visible text.
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;
}
// 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));
}
}
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
fn insert(&mut self, index: usize, table_entry: TableEntry) {
self.table.insert(index, table_entry);
self.text_up_to_date = false;
}
/// Add a table entry to the end of the table
pub(crate) fn push(&mut self, table_entry: TableEntry) {
self.table.push(table_entry);
self.text_up_to_date = false;
}
}
#[derive(Copy, Clone)] // Needed for PieceTable.actions
#[derive(Debug)]
/// An entry in PieceTable's table
pub(crate) struct TableEntry {
/// Whether this table entry points to the add buffer
is_add_buffer: bool,
/// Start index
start_index: usize,
/// End index
end_index: usize,
/// Whether this table is visible
pub(crate) 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}
}
/// Change from active to deactivated and visa versa
fn switch(&mut self) {
self.active = !self.active;
}
}
#[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>,
}
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()}
}
fn text(&self) -> &str {
&self.text
}
/// 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
}
/// Add a text piece to text pieces
fn add(&mut self, text: String) {
self.text_pieces.push(text);
self.text_up_to_date = false;
}
/// 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();
}
}
}