mirror of https://github.com/andrewkdinh/via.git
Worked on TUI
This commit is contained in:
parent
277fe8092f
commit
5fe766fd59
|
@ -1,32 +1,11 @@
|
||||||
# This file is automatically @generated by Cargo.
|
# This file is automatically @generated by Cargo.
|
||||||
# It is not intended for manual editing.
|
# 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]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.72"
|
version = "0.2.72"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a9f8082297d534141b30c8d39e9b1773713ab50fdbe4ff30f750d063b3bfd701"
|
checksum = "a9f8082297d534141b30c8d39e9b1773713ab50fdbe4ff30f750d063b3bfd701"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "memchr"
|
|
||||||
version = "2.3.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "numtoa"
|
name = "numtoa"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -48,24 +27,6 @@ dependencies = [
|
||||||
"redox_syscall",
|
"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]]
|
[[package]]
|
||||||
name = "termion"
|
name = "termion"
|
||||||
version = "1.5.5"
|
version = "1.5.5"
|
||||||
|
@ -78,26 +39,9 @@ dependencies = [
|
||||||
"redox_termios",
|
"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 = "unicode-segmentation"
|
|
||||||
version = "1.7.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "via"
|
name = "via"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"regex",
|
|
||||||
"termion",
|
"termion",
|
||||||
"unicode-segmentation",
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -15,5 +15,4 @@ categories = ["command-line-utilities", "text-editors"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
termion = "^1.5"
|
termion = "^1.5"
|
||||||
regex = "^1.3"
|
# unicode-segmentation = "^1.7.1"
|
||||||
unicode-segmentation = "^1.7.1"
|
|
||||||
|
|
18
README.md
18
README.md
|
@ -1,7 +1,21 @@
|
||||||
# via
|
# via
|
||||||
|
|
||||||
An efficient text editor
|
An efficient text editor written in Rust. If you've ever used Vim, you'll immediately be able to use Via. However, Via was built from the ground up and is a very separate project from any other text editor out there. It runs anywhere Rust can compile, and only has one external dependency to get you started as soon as possible. It even supports using a mouse! It has many structures like a piece table that would be useful in other projects as well. This project was done as a proof of concept for future, more GUI-oriented text editors.
|
||||||
|
|
||||||
**This software is in alpha. Don't use it for anything important**
|
**This software is in alpha. Don't use it for anything important**
|
||||||
|
|
||||||
Mirrors: [GitHub](https://github.com/andrewkdinh/via) (main), [GitLab](https://gitlab.com/andrewkdinh/via), [Gitea](https://gitea.andrewkdinh.com/andrewkdinh/via)
|
## Compiling
|
||||||
|
|
||||||
|
1. [Install Rust](https://www.rust-lang.org/tools/install)
|
||||||
|
2. `git clone https://github.com/andrewkdinh/via.git && cd via && cargo run`
|
||||||
|
3. Enjoy!
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
- Build with [Rust](https://rust-lang.org/) and [Termion](https://gitlab.redox-os.org/redox-os/termion/)
|
||||||
|
|
||||||
|
If you're interested in publishing this software on any software repository (AUR, PPA, etc.), please reach out to me (contact info available on my [website](https://andrewkdinh.com)).
|
||||||
|
|
||||||
|
Mirrors: [GitHub](https://github.com/andrewkdinh/via) (main), [GitLab](https://gitlab.com/andrewkdinh/via), [Gitea](https://gitea.andrewkdinh.com/andrewkdinh/via)
|
||||||
|
|
||||||
|
Licensed under [AGPL-3.0](./LICENSE) | Copyright (c) 2021 Andrew Dinh
|
||||||
|
|
113
src/main.rs
113
src/main.rs
|
@ -9,113 +9,12 @@ mod modules;
|
||||||
use modules::via::Via;
|
use modules::via::Via;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// let mut via = Via::new(env::args().collect());
|
let (mut file_paths, options) = Via::process_args(env::args().collect());
|
||||||
/*
|
if file_paths.is_empty() {
|
||||||
let mut editor = initialize(process_args(&args));
|
file_paths.push(String::new());
|
||||||
// Print the current file text onto screen
|
|
||||||
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)).unwrap();
|
|
||||||
|
|
||||||
for line in editor.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();
|
for file_path in file_paths {
|
||||||
|
let mut via = Via::new(file_path, options.clone());
|
||||||
let mut buffer_index = editor.piece_table.text_len();
|
via.init();
|
||||||
let mut cmd_index = 0;
|
|
||||||
let mut cmd_piece_table = piece_table::PieceTable::new();
|
|
||||||
let mut mode = 1; // [0: insert, 1: visual, 2: command]
|
|
||||||
for c in stdin.keys() {
|
|
||||||
write!(stdout,
|
|
||||||
"{}{}",
|
|
||||||
termion::cursor::Goto(1, 1),
|
|
||||||
termion::clear::CurrentLine)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
editor.process(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();
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{BufRead, BufReader};
|
use std::io::{BufRead, BufReader};
|
||||||
// use std::env;
|
use std::cmp::{min, max};
|
||||||
use std::cmp::min;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use super::piece_table::PieceTable;
|
use super::piece_table::PieceTable;
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
/// An editor window
|
/// An editor window
|
||||||
pub(crate) struct Editor {
|
pub(crate) struct Editor {
|
||||||
/// The piece table
|
/// The piece table
|
||||||
|
@ -68,40 +66,200 @@ impl Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns visible text
|
/// Returns visible text
|
||||||
fn text(&mut self) -> &str {
|
pub(crate) fn text(&mut self) -> &str {
|
||||||
self.piece_table.text()
|
self.piece_table.text()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds `text` at the current cursor position
|
/// Returns file path
|
||||||
fn add_text(&mut self, text: String) {
|
pub(crate) fn file_path(&self) -> &str {
|
||||||
let mut num_lines = 0;
|
self.file_path.as_str()
|
||||||
let mut last_line_len = self.col_want - 1;
|
}
|
||||||
for (i, line) in text.split("\n").enumerate() {
|
|
||||||
// TODO: Insert text to visual editor
|
/// Update the file path
|
||||||
if self.row + i - 1 >= self.lines.len() {
|
pub(crate) fn update_file_path(&mut self, file_path: String) {
|
||||||
self.lines.push(line.len());
|
self.file_path = file_path;
|
||||||
} else if i == 0 {
|
}
|
||||||
*(self.lines.get_mut(self.row + i - 1).unwrap()) += line.len();
|
|
||||||
|
/// Returns the current row
|
||||||
|
pub(crate) fn row(&self) -> usize {
|
||||||
|
self.row
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the current column
|
||||||
|
pub(crate) fn col(&self) -> usize {
|
||||||
|
self.col
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of columns in the specified `row` (1-indexed)
|
||||||
|
pub(crate) fn num_cols(&self, row: usize) -> usize {
|
||||||
|
*self.lines.get(row - 1).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of lines
|
||||||
|
pub(crate) fn num_lines(&self) -> usize {
|
||||||
|
self.lines.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the length of the line (1-indexed)
|
||||||
|
pub(crate) fn line_len(&self, line: usize) -> usize {
|
||||||
|
*self.lines.get(line - 1).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether the text of the file matches the text of `self.piece_table`
|
||||||
|
pub(crate) fn text_matches(&self) -> bool {
|
||||||
|
!self.piece_table.actions_taken()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns visible text from line `first` (inclusive) to `last` (exclusive)
|
||||||
|
pub(crate) fn text_lines(&mut self, first: usize, last: usize) -> &str {
|
||||||
|
if first >= last {
|
||||||
|
panic!("First row ({}) must be less last ({})", first, last);
|
||||||
|
}
|
||||||
|
let mut start_index = 0;
|
||||||
|
let mut end_index = 0;
|
||||||
|
for (i, line) in self.lines.iter().enumerate() {
|
||||||
|
if i < first - 1 {
|
||||||
|
start_index += line + 1;
|
||||||
|
end_index = start_index;
|
||||||
|
} else if i >= last - 1 {
|
||||||
|
break
|
||||||
} else {
|
} else {
|
||||||
self.lines.insert(self.row + i - 1, line.len());
|
end_index += line + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.piece_table.text().get(start_index..end_index - 1).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the visible text for a single row
|
||||||
|
pub(crate) fn text_line(&mut self, line: usize) -> &str {
|
||||||
|
self.text_lines(line, line + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds `text` at the current cursor position
|
||||||
|
pub(crate) fn add_text(&mut self, text: String) {
|
||||||
|
let mut from_end = 0;
|
||||||
|
let mut num_lines = 0;
|
||||||
|
let text_len = text.len();
|
||||||
|
let mut last_line_len = 0;
|
||||||
|
for (i, line) in text.split('\n').enumerate() {
|
||||||
|
if i == 0 {
|
||||||
|
let curr_line_len = self.lines.get_mut(self.row - 1).unwrap();
|
||||||
|
from_end = *curr_line_len + 1 - self.col;
|
||||||
|
if text.contains('\n') {
|
||||||
|
*curr_line_len -= from_end;
|
||||||
|
}
|
||||||
|
*curr_line_len += line.len();
|
||||||
|
} else if self.row + i >= self.lines.len() {
|
||||||
|
self.lines.push(line.len() + from_end);
|
||||||
|
from_end = 0;
|
||||||
|
} else {
|
||||||
|
self.lines.insert(self.row + i, line.len() + from_end);
|
||||||
|
from_end = 0;
|
||||||
}
|
}
|
||||||
num_lines += 1;
|
num_lines += 1;
|
||||||
last_line_len = line.len();
|
last_line_len = line.len();
|
||||||
}
|
}
|
||||||
self.piece_table.add_text(text, self.pt_index);
|
self.piece_table.add_text(text, self.pt_index);
|
||||||
if num_lines == 1 {
|
if num_lines == 1 {
|
||||||
self.right(last_line_len).unwrap();
|
self.right(text_len);
|
||||||
} else {
|
} else {
|
||||||
self.down(num_lines - 1).unwrap();
|
self.down(num_lines - 1);
|
||||||
self.goto_col(last_line_len + 1).unwrap();
|
self.goto_col(last_line_len + 1);
|
||||||
println!("{:?}", self.lines);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Deletes from current cursor position to (row, col) which are 1-indexed
|
||||||
|
pub(crate) fn delete_text(&mut self, row: usize, col: usize) -> Result<(), String> {
|
||||||
|
if row == self.row {
|
||||||
|
if row == self.row && col == self.col {
|
||||||
|
return Ok(())
|
||||||
|
// return Err("No text to delete".to_string());
|
||||||
|
}
|
||||||
|
let line_len = self.lines.get_mut(row - 1).unwrap();
|
||||||
|
let first_col = min(self.col, col);
|
||||||
|
let last_col = if first_col == self.col {col} else {self.col};
|
||||||
|
if last_col > *line_len + 1 {
|
||||||
|
// panic!("Can't delete from {} to {} of line length {}", first_col, last_col, *line_len);
|
||||||
|
return Err(format!("Can't delete from {} to {} of line length {}", first_col, last_col, *line_len))
|
||||||
|
}
|
||||||
|
let len = last_col - first_col;
|
||||||
|
*(line_len) -= len;
|
||||||
|
if first_col == self.col {
|
||||||
|
self.piece_table.delete_text(self.pt_index, self.pt_index + len);
|
||||||
|
} else {
|
||||||
|
self.piece_table.delete_text(self.pt_index - len, self.pt_index);
|
||||||
|
self.col -= len;
|
||||||
|
self.col_want = self.col;
|
||||||
|
}
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut size = 0;
|
||||||
|
let first_row = min(self.row, row);
|
||||||
|
let first_col = if first_row == self.row {self.col} else {col};
|
||||||
|
let last_row = if first_row == self.row {row} else {self.row};
|
||||||
|
let last_col = if first_row == self.row {col} else {self.col};
|
||||||
|
|
||||||
|
if last_row == usize::MAX {
|
||||||
|
// TODO: Don't actually read to end of file. Just pretend you did
|
||||||
|
// If you do this, you have to update undo and redo to update self.eof_reached
|
||||||
|
self.read_to_eof()
|
||||||
|
} else {
|
||||||
|
self.read_lines(max(self.lines.len(), row) - min(self.lines.len(), row));
|
||||||
|
}
|
||||||
|
|
||||||
|
let first_line_len = self.lines.get_mut(first_row - 1).unwrap();
|
||||||
|
if first_col > *first_line_len + 1 {
|
||||||
|
panic!("Invalid beginning column {} for row {}", first_col, first_row);
|
||||||
|
}
|
||||||
|
size += *first_line_len + 1 - (first_col - 1);
|
||||||
|
*first_line_len -= *first_line_len - (first_col - 1);
|
||||||
|
let first_line_len_copy = *first_line_len;
|
||||||
|
let to_last_row = last_row == self.lines.len();
|
||||||
|
for _ in first_row + 1..last_row {
|
||||||
|
let line_len = self.lines.remove(first_row);
|
||||||
|
size += line_len + 1;
|
||||||
|
}
|
||||||
|
let last_line_len = self.lines.get_mut(last_row - (last_row - first_row) - 1).unwrap();
|
||||||
|
if last_col - 1 > *last_line_len {
|
||||||
|
panic!("Invalid ending column {} for row {}", last_col, last_row);
|
||||||
|
}
|
||||||
|
*(last_line_len) -= last_col - 1;
|
||||||
|
size += last_col - 1;
|
||||||
|
if to_last_row {
|
||||||
|
self.lines.pop();
|
||||||
|
}
|
||||||
|
if first_row == self.row {
|
||||||
|
self.piece_table.delete_text(self.pt_index, self.pt_index + size);
|
||||||
|
} else {
|
||||||
|
self.piece_table.delete_text(self.pt_index - size, self.pt_index);
|
||||||
|
self.row = first_row;
|
||||||
|
self.col = first_line_len_copy;
|
||||||
|
self.col_want = self.col;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete all text
|
||||||
|
pub(crate) fn delete_all(&mut self) {
|
||||||
|
if self.piece_table.text_len() == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.piece_table.delete_text(0, self.piece_table.text_len());
|
||||||
|
self.row = 1;
|
||||||
|
self.col = 1;
|
||||||
|
self.col_want = 1;
|
||||||
|
self.pt_index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn delete_to_end(&mut self) {
|
||||||
|
self.delete_text(usize::MAX, usize::MAX).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
/// Read `num_lines` from `reader`, updating `self.piece_table` & `self.lines`
|
/// Read `num_lines` from `reader`, updating `self.piece_table` & `self.lines`
|
||||||
/// Returns number of lines actually read
|
/// Returns number of lines actually read
|
||||||
fn read_lines(&mut self, num_lines: usize) -> usize {
|
fn read_lines(&mut self, num_lines: usize) -> usize {
|
||||||
if self.eof_reached {
|
if num_lines == 0 || self.eof_reached {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
let mut lines_read = 0;
|
let mut lines_read = 0;
|
||||||
|
@ -126,7 +284,6 @@ impl Editor {
|
||||||
|
|
||||||
/// Read to EOF, updating `self.piece_table` & `self.lines`
|
/// Read to EOF, updating `self.piece_table` & `self.lines`
|
||||||
fn read_to_eof(&mut self) {
|
fn read_to_eof(&mut self) {
|
||||||
// Maybe use self.read_lines(usize::MAX) instead?
|
|
||||||
if self.eof_reached {
|
if self.eof_reached {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -148,29 +305,33 @@ impl Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Move the cursor up `num` places
|
/// Move the cursor up `num` places
|
||||||
pub(crate) fn up(&mut self, num: usize) -> Result<(), String> {
|
/// If unable to go up all the way, go to first row
|
||||||
if self.row == 1 || num >= self.row {
|
pub(crate) fn up(&mut self, num: usize) {
|
||||||
return Err("Can't go up".to_string());
|
if num == 0 || self.row == 1 {
|
||||||
|
return
|
||||||
|
} else if num >= self.row {
|
||||||
|
self.up(self.row - 1);
|
||||||
|
return
|
||||||
}
|
}
|
||||||
self.pt_index -= self.col + 1;
|
self.pt_index -= self.col;
|
||||||
for i in 1..num {
|
for i in 1..num {
|
||||||
self.pt_index -= self.lines.get(self.row - i).unwrap() + 1;
|
self.pt_index -= self.lines.get(self.row - i - 1).unwrap() + 1;
|
||||||
}
|
}
|
||||||
self.row -= num;
|
self.row -= num;
|
||||||
let line_cols = self.lines.get(self.row - 1).unwrap();
|
let line_cols = self.lines.get(self.row - 1).unwrap();
|
||||||
self.col = min(self.col_want, line_cols + 1);
|
self.col = min(self.col_want, line_cols + 1);
|
||||||
self.pt_index -= line_cols + 1 - self.col;
|
self.pt_index -= line_cols + 1 - self.col;
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Move the cursor down `num` places
|
/// Move the cursor down `num` places.
|
||||||
pub(crate) fn down(&mut self, num: usize) -> Result<(), String> {
|
/// If unable to go all the way down, go to last row
|
||||||
if self.row + num > self.lines.len() {
|
pub(crate) fn down(&mut self, num: usize) {
|
||||||
let from_bottom = self.row + num - self.lines.len();
|
if num == 0 {
|
||||||
let lines_read = self.read_lines(from_bottom);
|
return
|
||||||
for _ in lines_read..from_bottom {
|
} else if self.row + num > self.lines.len() {
|
||||||
self.lines.push(0);
|
let lines_read = self.read_lines(self.row + num - self.lines.len());
|
||||||
}
|
self.down(lines_read);
|
||||||
|
return
|
||||||
}
|
}
|
||||||
self.pt_index += self.lines.get(self.row - 1).unwrap() + 1 - self.col + 1;
|
self.pt_index += self.lines.get(self.row - 1).unwrap() + 1 - self.col + 1;
|
||||||
for i in 1..num {
|
for i in 1..num {
|
||||||
|
@ -179,39 +340,43 @@ impl Editor {
|
||||||
self.row += num;
|
self.row += num;
|
||||||
self.col = min(self.col_want, self.lines.get(self.row - 1).unwrap() + 1);
|
self.col = min(self.col_want, self.lines.get(self.row - 1).unwrap() + 1);
|
||||||
self.pt_index += self.col - 1;
|
self.pt_index += self.col - 1;
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Move the cursor right `num` places
|
/// Move the cursor right `num` places.
|
||||||
pub(crate) fn right(&mut self, num: usize) -> Result<(), String> {
|
/// If unable to go all the way right, go to last column
|
||||||
let line_cols = self.lines.get(self.row - 1).unwrap() + 1;
|
pub(crate) fn right(&mut self, num: usize) {
|
||||||
if self.col + num > line_cols {
|
let line_len = self.lines.get(self.row - 1).unwrap();
|
||||||
return Err("Can't go right".to_string());
|
if num == 0 || self.col == line_len + 1 {
|
||||||
|
return
|
||||||
|
} else if self.col + num > line_len + 1 {
|
||||||
|
self.goto_last_col();
|
||||||
|
return
|
||||||
}
|
}
|
||||||
self.col += num;
|
self.col += num;
|
||||||
self.pt_index += num;
|
self.pt_index += num;
|
||||||
self.col_want = self.col;
|
self.col_want = self.col;
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Move the cursor left `num` places
|
/// Move the cursor left `num` places.
|
||||||
pub(crate) fn left(&mut self, num: usize) -> Result<(), String> {
|
/// If unable to go all the way left, go to first column
|
||||||
if num >= self.col {
|
pub(crate) fn left(&mut self, num: usize) {
|
||||||
return Err("Can't go left".to_string());
|
if num == 0 {
|
||||||
|
return
|
||||||
|
} else if num >= self.col {
|
||||||
|
self.left(self.col - 1);
|
||||||
|
return
|
||||||
}
|
}
|
||||||
self.col -= num;
|
self.col -= num;
|
||||||
self.pt_index -= num;
|
self.pt_index -= num;
|
||||||
self.col_want = self.col;
|
self.col_want = self.col;
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Move to a certain column in the current row
|
/// Move to a certain column in the current row
|
||||||
pub(crate) fn goto_col(&mut self, col: usize) -> Result<(), String> {
|
pub(crate) fn goto_col(&mut self, col: usize) {
|
||||||
if col > *self.lines.get(self.row - 1).unwrap() + 1 {
|
if col > *self.lines.get(self.row - 1).unwrap() + 1 {
|
||||||
return Err("col greater than columns in row".to_string());
|
self.goto_last_col();
|
||||||
}
|
} else if self.col == col {
|
||||||
if self.col == col {
|
self.col_want = col;
|
||||||
Ok(())
|
|
||||||
} else if col < self.col {
|
} else if col < self.col {
|
||||||
self.left(self.col - col)
|
self.left(self.col - col)
|
||||||
} else {
|
} else {
|
||||||
|
@ -220,9 +385,9 @@ impl Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Move to a certain row
|
/// Move to a certain row
|
||||||
pub(crate) fn goto_row(&mut self, row: usize) -> Result<(), String> {
|
pub(crate) fn goto_row(&mut self, row: usize) {
|
||||||
if self.row == row {
|
if self.row == row {
|
||||||
Ok(())
|
return
|
||||||
} else if row < self.row {
|
} else if row < self.row {
|
||||||
self.up(self.row - row)
|
self.up(self.row - row)
|
||||||
} else {
|
} else {
|
||||||
|
@ -230,9 +395,21 @@ impl Editor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Move to (closest) `row` and `col`
|
||||||
|
pub(crate) fn goto(&mut self, row: usize, col: usize) {
|
||||||
|
self.goto_row(row);
|
||||||
|
self.goto_col(col);
|
||||||
|
}
|
||||||
|
|
||||||
/// Move to the last column in the current row
|
/// Move to the last column in the current row
|
||||||
pub(crate) fn goto_last_col(&mut self) -> Result<(), String> {
|
pub(crate) fn goto_last_col(&mut self) {
|
||||||
self.goto_col(*self.lines.get(self.row - 1).unwrap())
|
self.goto_col(*self.lines.get(self.row - 1).unwrap() + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Move to the last row
|
||||||
|
pub(crate) fn goto_last_row(&mut self) {
|
||||||
|
self.read_to_eof();
|
||||||
|
self.goto(self.lines.len(), 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -242,68 +419,257 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn add_text() {
|
fn add_text() {
|
||||||
let mut editor = Editor::new("".to_string());
|
let mut editor = Editor::new(String::new());
|
||||||
let mut want_str = "hello";
|
let mut want_str = "hello";
|
||||||
editor.add_text(want_str.to_string());
|
editor.add_text(want_str.to_string());
|
||||||
assert_eq!(editor.text(), want_str);
|
assert_eq!(editor.text(), want_str);
|
||||||
|
|
||||||
editor = Editor::new("".to_string());
|
editor = Editor::new(String::new());
|
||||||
want_str = "hello\nbye";
|
want_str = "hello\nbye";
|
||||||
editor.add_text(want_str.to_string());
|
editor.add_text(want_str.to_string());
|
||||||
assert_eq!(editor.text(), want_str);
|
assert_eq!(editor.text(), want_str);
|
||||||
|
|
||||||
editor = Editor::new("".to_string());
|
editor = Editor::new(String::new());
|
||||||
editor.add_text("hello\n".to_string());
|
editor.add_text("hello\n".to_string());
|
||||||
editor.add_text("bye".to_string());
|
editor.add_text("bye".to_string());
|
||||||
want_str = "hello\nbye";
|
want_str = "hello\nbye";
|
||||||
assert_eq!(editor.text(), want_str);
|
assert_eq!(editor.text(), want_str);
|
||||||
|
|
||||||
editor = Editor::new("".to_string());
|
editor = Editor::new(String::new());
|
||||||
editor.add_text("hello\n\n".to_string());
|
editor.add_text("hello\n\n".to_string());
|
||||||
editor.add_text("bye".to_string());
|
editor.add_text("bye".to_string());
|
||||||
want_str = "hello\n\nbye";
|
want_str = "hello\n\nbye";
|
||||||
assert_eq!(editor.text(), want_str);
|
assert_eq!(editor.text(), want_str);
|
||||||
|
|
||||||
|
editor = Editor::new(String::new());
|
||||||
|
editor.add_text("hello".to_string());
|
||||||
|
editor.add_text("\nbye".to_string());
|
||||||
|
want_str = "hello\nbye";
|
||||||
|
assert_eq!(editor.text(), want_str);
|
||||||
|
|
||||||
|
editor = Editor::new(String::new());
|
||||||
|
editor.add_text("\nhello".to_string());
|
||||||
|
editor.add_text("\nbye".to_string());
|
||||||
|
want_str = "\nhello\nbye";
|
||||||
|
assert_eq!(editor.text(), want_str);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn delete_text() {
|
||||||
|
let mut editor = Editor::new(String::new());
|
||||||
|
let mut want_str = "";
|
||||||
|
editor.add_text("abcd".to_string());
|
||||||
|
editor.delete_text(1, 1).unwrap();
|
||||||
|
assert_eq!(editor.text(), want_str);
|
||||||
|
|
||||||
|
editor = Editor::new(String::new());
|
||||||
|
want_str = "ab\nef";
|
||||||
|
editor.add_text("ab\ncd\nef".to_string());
|
||||||
|
editor.goto(2, 1);
|
||||||
|
editor.delete_text(3, 1).unwrap();
|
||||||
|
assert_eq!(editor.text(), want_str);
|
||||||
|
assert_eq!(editor.num_lines(), want_str.lines().count());
|
||||||
|
|
||||||
|
editor = Editor::new(String::new());
|
||||||
|
want_str = "ab\n\ncd";
|
||||||
|
editor.add_text("ab\n\n\ncd".to_string());
|
||||||
|
editor.goto(3, 1);
|
||||||
|
editor.delete_text(4, 1).unwrap();
|
||||||
|
assert_eq!(editor.text(), want_str);
|
||||||
|
|
||||||
|
editor = Editor::new(String::new());
|
||||||
|
want_str = "ab\n\ncd";
|
||||||
|
editor.add_text("ab\n\n\ncd".to_string());
|
||||||
|
editor.goto(4, 1);
|
||||||
|
editor.delete_text(3, 1).unwrap();
|
||||||
|
assert_eq!(editor.text(), want_str);
|
||||||
|
|
||||||
|
editor = Editor::new(String::new());
|
||||||
|
editor.add_text("h".to_string());
|
||||||
|
editor.add_text("\n".to_string());
|
||||||
|
editor.add_text("b".to_string());
|
||||||
|
want_str = "h\nb";
|
||||||
|
assert_eq!(editor.text(), want_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
fn movement() {
|
fn movement() {
|
||||||
let mut editor = Editor::new("".to_string());
|
let mut editor = Editor::new(String::new());
|
||||||
let mut want_str = "hello\nbye";
|
let mut want_str = "hello\nbye";
|
||||||
editor.add_text("hello".to_string());
|
editor.add_text("hello\n".to_string());
|
||||||
editor.down(1).unwrap();
|
editor.down(1);
|
||||||
editor.add_text("bye".to_string());
|
editor.add_text("bye".to_string());
|
||||||
assert_eq!(editor.text(), want_str);
|
assert_eq!(editor.text(), want_str);
|
||||||
|
|
||||||
editor = Editor::new("".to_string());
|
editor = Editor::new(String::new());
|
||||||
want_str = "hello\nbye";
|
want_str = "hello\nbye";
|
||||||
editor.add_text("h".to_string());
|
editor.add_text("h\n".to_string());
|
||||||
editor.down(1).unwrap();
|
editor.down(1);
|
||||||
editor.add_text("bye".to_string());
|
editor.add_text("bye".to_string());
|
||||||
editor.up(1).unwrap();
|
editor.up(1);
|
||||||
editor.add_text("ello".to_string());
|
editor.add_text("ello".to_string());
|
||||||
assert_eq!(editor.text(), want_str);
|
assert_eq!(editor.text(), want_str);
|
||||||
|
|
||||||
editor = Editor::new("".to_string());
|
editor = Editor::new(String::new());
|
||||||
want_str = "ab\nabcd";
|
want_str = "ab\nabcd";
|
||||||
editor.add_text("ab".to_string());
|
editor.add_text("ab\n".to_string());
|
||||||
editor.down(1).unwrap();
|
editor.down(1);
|
||||||
editor.add_text("abc".to_string());
|
editor.add_text("abc".to_string());
|
||||||
editor.up(1).unwrap();
|
editor.up(1);
|
||||||
editor.down(1).unwrap();
|
editor.down(1);
|
||||||
editor.add_text("d".to_string());
|
editor.add_text("d".to_string());
|
||||||
assert_eq!(editor.text(), want_str);
|
assert_eq!(editor.text(), want_str);
|
||||||
|
|
||||||
editor = Editor::new("".to_string());
|
editor = Editor::new(String::new());
|
||||||
want_str = "abcde\na\n\na";
|
want_str = "abcde\na\n\na";
|
||||||
editor.add_text("acd".to_string());
|
editor.add_text("acd\n".to_string());
|
||||||
editor.down(1).unwrap();
|
editor.add_text("a\n\n".to_string());
|
||||||
editor.add_text("a".to_string());
|
assert_eq!(editor.text(), "acd\na\n\n");
|
||||||
editor.up(1).unwrap();
|
editor.up(3);
|
||||||
|
editor.right(1);
|
||||||
editor.add_text("b".to_string());
|
editor.add_text("b".to_string());
|
||||||
editor.goto_last_col().unwrap();
|
assert_eq!(editor.text(), "abcd\na\n\n");
|
||||||
|
editor.goto_last_col();
|
||||||
editor.add_text("e".to_string());
|
editor.add_text("e".to_string());
|
||||||
editor.down(3).unwrap();
|
assert_eq!(editor.text(), "abcde\na\n\n");
|
||||||
|
editor.down(3);
|
||||||
editor.add_text("a".to_string());
|
editor.add_text("a".to_string());
|
||||||
assert_eq!(editor.text(), want_str);
|
assert_eq!(editor.text(), want_str);
|
||||||
|
|
||||||
|
editor = Editor::new(String::new());
|
||||||
|
editor.add_text("he".to_string());
|
||||||
|
editor.left(1);
|
||||||
|
editor.add_text("\n".to_string());
|
||||||
|
editor.add_text("b".to_string());
|
||||||
|
want_str = "h\nbe";
|
||||||
|
assert_eq!(editor.text(), want_str);
|
||||||
|
|
||||||
|
editor = Editor::new(String::new());
|
||||||
|
editor.add_text("hellobye".to_string());
|
||||||
|
editor.left(3);
|
||||||
|
editor.add_text("\n".to_string());
|
||||||
|
want_str = "hello\nbye";
|
||||||
|
assert_eq!(editor.text(), want_str);
|
||||||
|
|
||||||
|
editor = Editor::new(String::new());
|
||||||
|
editor.add_text("helloye".to_string());
|
||||||
|
editor.left(2);
|
||||||
|
editor.add_text("\nb".to_string());
|
||||||
|
want_str = "hello\nbye";
|
||||||
|
assert_eq!(editor.text(), want_str);
|
||||||
|
|
||||||
|
editor = Editor::new(String::new());
|
||||||
|
editor.add_text("abc".to_string());
|
||||||
|
editor.left(2);
|
||||||
|
editor.add_text("d\n".to_string());
|
||||||
|
editor.add_text("e".to_string());
|
||||||
|
want_str = "ad\nebc";
|
||||||
|
assert_eq!(editor.text(), want_str);
|
||||||
|
|
||||||
|
editor = Editor::new(String::new());
|
||||||
|
editor.add_text("helloe".to_string());
|
||||||
|
editor.left(1);
|
||||||
|
editor.add_text("\nb".to_string());
|
||||||
|
editor.add_text("y".to_string());
|
||||||
|
want_str = "hello\nbye";
|
||||||
|
assert_eq!(editor.text(), want_str);
|
||||||
|
|
||||||
|
editor = Editor::new(String::new());
|
||||||
|
editor.add_text("hellye".to_string());
|
||||||
|
editor.left(2);
|
||||||
|
editor.add_text("\nb".to_string());
|
||||||
|
editor.up(1);
|
||||||
|
editor.goto_last_col();
|
||||||
|
editor.add_text("o".to_string());
|
||||||
|
want_str = "hello\nbye";
|
||||||
|
assert_eq!(editor.text(), want_str);
|
||||||
|
|
||||||
|
editor = Editor::new(String::new());
|
||||||
|
editor.add_text("hello".to_string());
|
||||||
|
editor.add_text("\n".to_string());
|
||||||
|
editor.add_text("by".to_string());
|
||||||
|
editor.up(1);
|
||||||
|
editor.goto_last_col();
|
||||||
|
editor.add_text("\n".to_string());
|
||||||
|
editor.add_text("and".to_string());
|
||||||
|
editor.down(1);
|
||||||
|
editor.goto_last_col();
|
||||||
|
editor.add_text("e".to_string());
|
||||||
|
want_str = "hello\nand\nbye";
|
||||||
|
assert_eq!(editor.text(), want_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn edge_cases() {
|
||||||
|
let mut editor = Editor::new(String::new());
|
||||||
|
let mut want_str = "hell";
|
||||||
|
editor.add_text("hello\n\n".to_string());
|
||||||
|
editor.delete_text(1, 5).unwrap();
|
||||||
|
assert_eq!(editor.text(), want_str);
|
||||||
|
|
||||||
|
editor = Editor::new(String::new());
|
||||||
|
want_str = "hello";
|
||||||
|
editor.add_text("hello\n\n".to_string());
|
||||||
|
editor.delete_text(1, 6).unwrap();
|
||||||
|
assert_eq!(editor.text(), want_str);
|
||||||
|
|
||||||
|
editor = Editor::new(String::new());
|
||||||
|
want_str = "";
|
||||||
|
editor.add_text("\n\n".to_string());
|
||||||
|
editor.delete_text(1, 1).unwrap();
|
||||||
|
assert_eq!(editor.text(), want_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn more_cases() {
|
||||||
|
let mut editor = Editor::new(String::new());
|
||||||
|
let mut want_str = "hello\nbye";
|
||||||
|
editor.add_text("hello\n\n".to_string());
|
||||||
|
editor.add_text("bye".to_string());
|
||||||
|
editor.up(2);
|
||||||
|
editor.goto_last_col();
|
||||||
|
editor.delete_text(editor.row() + 1, 1).unwrap();
|
||||||
|
assert_eq!(editor.text(), want_str);
|
||||||
|
println!("{:?}", editor.lines);
|
||||||
|
assert_eq!(editor.num_lines(), want_str.lines().count());
|
||||||
|
|
||||||
|
editor = Editor::new(String::new());
|
||||||
|
want_str = "hellobye";
|
||||||
|
editor.add_text("hello\n\n".to_string());
|
||||||
|
editor.add_text("bye".to_string());
|
||||||
|
editor.up(2);
|
||||||
|
editor.goto_last_col();
|
||||||
|
editor.delete_text(editor.row() + 2, 1).unwrap();
|
||||||
|
assert_eq!(editor.text(), want_str);
|
||||||
|
assert_eq!(editor.num_lines(), want_str.lines().count());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn text_lines() {
|
||||||
|
let mut editor = Editor::new(String::new());
|
||||||
|
let mut want_str = "abc";
|
||||||
|
editor.add_text("abc".to_string());
|
||||||
|
assert_eq!(editor.text_lines(1, 2), want_str);
|
||||||
|
|
||||||
|
editor = Editor::new(String::new());
|
||||||
|
want_str = "abc";
|
||||||
|
editor.add_text("abc\n\ncd".to_string());
|
||||||
|
assert_eq!(editor.text_line(1), want_str);
|
||||||
|
|
||||||
|
editor = Editor::new(String::new());
|
||||||
|
want_str = "abc";
|
||||||
|
editor.add_text("abc\n\ncd".to_string());
|
||||||
|
assert_eq!(editor.text_lines(1, 2), want_str);
|
||||||
|
|
||||||
|
editor = Editor::new(String::new());
|
||||||
|
want_str = "";
|
||||||
|
editor.add_text("abc\n\ncd".to_string());
|
||||||
|
assert_eq!(editor.text_line(2), want_str);
|
||||||
|
|
||||||
|
editor = Editor::new(String::new());
|
||||||
|
want_str = "\ncd\ne";
|
||||||
|
editor.add_text("abc\n\ncd\ne".to_string());
|
||||||
|
assert_eq!(editor.text_lines(2, 5), want_str);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
// use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
/// The main structure for storing text
|
/// The main structure for storing text
|
||||||
pub(crate) struct PieceTable {
|
pub(crate) struct PieceTable {
|
||||||
/// The main table, contains `TableEntry`'s
|
/// The main table, contains `TableEntry`'s
|
||||||
|
@ -38,6 +37,11 @@ impl PieceTable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns if any actions have been taken
|
||||||
|
pub(crate) fn actions_taken(&self) -> bool {
|
||||||
|
!self.actions.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
/// Append text to the original buffer and add a table entry
|
/// Append text to the original buffer and add a table entry
|
||||||
pub(crate) fn update_original_buffer(&mut self, text: String) {
|
pub(crate) fn update_original_buffer(&mut self, text: String) {
|
||||||
let org_buffer_len = self.original_buffer.len();
|
let org_buffer_len = self.original_buffer.len();
|
||||||
|
@ -230,7 +234,7 @@ impl PieceTable {
|
||||||
/// Returns the text represented by a table entry
|
/// Returns the text represented by a table entry
|
||||||
fn table_entry_text(&self, table_entry: &TableEntry) -> &str {
|
fn table_entry_text(&self, table_entry: &TableEntry) -> &str {
|
||||||
let buffer = if table_entry.is_add_buffer {&self.add_buffer} else {&self.original_buffer};
|
let buffer = if table_entry.is_add_buffer {&self.add_buffer} else {&self.original_buffer};
|
||||||
buffer.get(table_entry.start_index..table_entry.end_index).unwrap() // TODO: Get this working with Unicode
|
buffer.get(table_entry.start_index..table_entry.end_index).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns length of text
|
/// Returns length of text
|
||||||
|
@ -287,8 +291,6 @@ impl PieceTable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone)] // Needed for PieceTable.actions
|
|
||||||
#[derive(Debug)]
|
|
||||||
/// An entry in PieceTable's table
|
/// An entry in PieceTable's table
|
||||||
struct TableEntry {
|
struct TableEntry {
|
||||||
/// Whether this table entry points to the add buffer
|
/// Whether this table entry points to the add buffer
|
||||||
|
@ -471,7 +473,7 @@ mod tests {
|
||||||
piece_table.add_text("😀".to_string(), 0);
|
piece_table.add_text("😀".to_string(), 0);
|
||||||
want_str = "😀";
|
want_str = "😀";
|
||||||
assert_eq!(want_str.len(), 4);
|
assert_eq!(want_str.len(), 4);
|
||||||
assert_eq!(want_str.graphemes(true).count(), 1);
|
// assert_eq!(want_str.graphemes(true).count(), 1);
|
||||||
assert_eq!(piece_table.text_len, want_str.len());
|
assert_eq!(piece_table.text_len, want_str.len());
|
||||||
assert_eq!(piece_table.text(), want_str);
|
assert_eq!(piece_table.text(), want_str);
|
||||||
|
|
||||||
|
@ -480,7 +482,7 @@ mod tests {
|
||||||
want_str = "é";
|
want_str = "é";
|
||||||
assert_eq!(want_str.len(), 3);
|
assert_eq!(want_str.len(), 3);
|
||||||
assert_eq!(want_str.chars().count(), 2);
|
assert_eq!(want_str.chars().count(), 2);
|
||||||
assert_eq!(want_str.graphemes(true).count(), 1);
|
// assert_eq!(want_str.graphemes(true).count(), 1);
|
||||||
assert_eq!(piece_table.text_len, want_str.len());
|
assert_eq!(piece_table.text_len, want_str.len());
|
||||||
assert_eq!(piece_table.text(), want_str);
|
assert_eq!(piece_table.text(), want_str);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,76 +1,312 @@
|
||||||
extern crate termion;
|
extern crate termion;
|
||||||
extern crate regex;
|
|
||||||
|
|
||||||
// use termion::event::Key;
|
use termion::event::{Key, Event, MouseEvent};
|
||||||
|
use termion::input::{TermRead, MouseTerminal};
|
||||||
|
use termion::raw::IntoRawMode;
|
||||||
|
|
||||||
use regex::Regex;
|
use std::cmp::min;
|
||||||
|
use std::io::{Write, stdout, stdin};
|
||||||
|
use std::convert::TryInto;
|
||||||
|
use std::fs::File;
|
||||||
|
|
||||||
use super::editor::Editor;
|
use super::editor::Editor;
|
||||||
use super::piece_table::PieceTable;
|
use super::piece_table::PieceTable;
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
/// Via main class, comprised of `Editor`'s
|
/// Via main class, comprised of `Editor`'s
|
||||||
pub struct Via {
|
pub struct Via {
|
||||||
/// List of current editors
|
/// List of current editors
|
||||||
editors: Vec<Editor>,
|
editors: Vec<Editor>,
|
||||||
/// List of files to edit
|
/// Editor representing the command line
|
||||||
file_paths: Vec<String>,
|
cmd_editor: Editor,
|
||||||
/// Configured options
|
/// Configured options
|
||||||
options: ViaOptions,
|
options: ViaOptions,
|
||||||
|
/** Mode Via is currently in
|
||||||
|
* 0: normal
|
||||||
|
* 1: visual (not implemented)
|
||||||
|
* 2: select (not implemented)
|
||||||
|
* 3: insert
|
||||||
|
* 4: command line
|
||||||
|
* 5: ex (not implemented)
|
||||||
|
*/
|
||||||
|
mode: usize,
|
||||||
/// Piece table of the command line
|
/// Piece table of the command line
|
||||||
cmd_piece_table: PieceTable,
|
cmd_piece_table: PieceTable,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Via {
|
impl Via {
|
||||||
/// Initialize a new instance of Via from arguments
|
/// Initialize a new instance of Via from arguments
|
||||||
pub fn new(args: Vec<String>) -> Via {
|
pub(crate) fn new(file_path: String, options: ViaOptions) -> Via {
|
||||||
let (mut file_paths, options) = process_args(args);
|
|
||||||
if file_paths.is_empty() {
|
|
||||||
file_paths.push("".to_string());
|
|
||||||
}
|
|
||||||
Via {
|
Via {
|
||||||
editors: vec![Editor::new((*file_paths.first().unwrap()).as_str().to_string())],
|
editors: vec![Editor::new(file_path)],
|
||||||
file_paths: file_paths,
|
cmd_editor: Editor::new("".to_string()),
|
||||||
options: options,
|
options: options,
|
||||||
|
mode: 0,
|
||||||
cmd_piece_table: PieceTable::new(),
|
cmd_piece_table: PieceTable::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/// Initialize Via and start editing
|
||||||
fn initialize(editor_options: editor::ViaOptions) -> editor::Editor {
|
pub fn init(&mut self) {
|
||||||
// TODO: Might not want to create file, but instead write to memory then at the end then write to file
|
// let stdin = termion::async_stdin();
|
||||||
let file_name = &editor_options.file_name;
|
let stdin = stdin();
|
||||||
let file_path = Path::new(&file_name);
|
let mut stdout = MouseTerminal::from(stdout().into_raw_mode().unwrap());
|
||||||
let file: File;
|
|
||||||
if !file_path.is_file() {
|
write!(stdout, "{}{}{}", termion::clear::All, termion::cursor::Goto(1, 1), termion::cursor::BlinkingBlock).unwrap();
|
||||||
panic!("{} is not a file", file_name);
|
stdout.flush().unwrap();
|
||||||
} else if file_path.exists() {
|
|
||||||
file = File::open(file_name).expect("Failed to open file");
|
// TODO: Implement support for multiple editors concurrently
|
||||||
} else {
|
let editor = self.editors.get_mut(0).unwrap();
|
||||||
File::create(file_path).expect("Unable to create file");
|
let mut visual_first_row: usize = 1;
|
||||||
file = File::open(file_name).expect("Failed to open file");
|
|
||||||
|
// TODO: Remove me
|
||||||
|
editor.goto_last_row();
|
||||||
|
editor.goto_last_col();
|
||||||
|
|
||||||
|
let mut full_render = false;
|
||||||
|
for c in stdin.events() {
|
||||||
|
let (term_rows_u16, term_cols_u16) = termion::terminal_size().unwrap();
|
||||||
|
let term_rows: usize = term_rows_u16.try_into().unwrap();
|
||||||
|
let _term_cols: usize = term_cols_u16.try_into().unwrap();
|
||||||
|
let evt = c.unwrap();
|
||||||
|
match evt {
|
||||||
|
Event::Key(key) => {
|
||||||
|
if key == Key::Esc {
|
||||||
|
if self.mode == 4 {
|
||||||
|
write!(stdout, "{}", termion::cursor::Restore).unwrap();
|
||||||
|
} else if self.mode == 3 {
|
||||||
|
editor.left(1);
|
||||||
|
}
|
||||||
|
self.mode = 0;
|
||||||
|
} else if self.mode == 0 {
|
||||||
|
// Normal mode
|
||||||
|
match key {
|
||||||
|
Key::Char('h') | Key::Left | Key::Backspace => editor.left(1),
|
||||||
|
Key::Char('j') | Key::Down => {
|
||||||
|
if visual_first_row + term_rows == editor.row() {
|
||||||
|
visual_first_row += 1;
|
||||||
|
}
|
||||||
|
editor.down(1);
|
||||||
|
if editor.col() - 1 == editor.line_len(editor.row()) {
|
||||||
|
editor.left(1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Key::Char('\n') => {
|
||||||
|
if visual_first_row + term_rows == editor.row() {
|
||||||
|
visual_first_row += 1;
|
||||||
|
}
|
||||||
|
if editor.row() != editor.num_lines() {
|
||||||
|
editor.goto(editor.row() + 1, 1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Key::Char('k') | Key::Up => {
|
||||||
|
editor.up(1);
|
||||||
|
if editor.col() - 1 == editor.line_len(editor.row()) {
|
||||||
|
editor.left(1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Key::Char('l') | Key:: Right => {
|
||||||
|
if editor.col() < editor.line_len(editor.row()) {
|
||||||
|
editor.right(1)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Key::Char('i') => self.mode = 3,
|
||||||
|
Key::Char('a') => {
|
||||||
|
editor.right(1);
|
||||||
|
self.mode = 3
|
||||||
|
},
|
||||||
|
Key::Char('o') => {
|
||||||
|
editor.goto_last_col();
|
||||||
|
editor.add_text("\n".to_string());
|
||||||
|
self.mode = 3;
|
||||||
|
full_render = true;
|
||||||
|
},
|
||||||
|
Key::Char('O') => {
|
||||||
|
editor.goto_col(0);
|
||||||
|
editor.add_text("\n".to_string());
|
||||||
|
editor.up(1);
|
||||||
|
self.mode = 3;
|
||||||
|
full_render = true;
|
||||||
|
},
|
||||||
|
Key::Char(':') => {
|
||||||
|
write!(stdout, "{}", termion::cursor::Save).unwrap();
|
||||||
|
self.cmd_editor.delete_all();
|
||||||
|
self.cmd_editor.add_text(":".to_string());
|
||||||
|
self.mode = 4
|
||||||
|
},
|
||||||
|
Key::Char('$') => {
|
||||||
|
editor.goto_last_col();
|
||||||
|
editor.left(1);
|
||||||
|
},
|
||||||
|
Key::Char('0') => {
|
||||||
|
editor.goto_col(0);
|
||||||
|
},
|
||||||
|
Key::Char('A') => {
|
||||||
|
editor.goto_last_col();
|
||||||
|
self.mode = 3;
|
||||||
|
}
|
||||||
|
Key::Delete => {
|
||||||
|
let at_line_end = editor.col() == editor.num_cols(editor.row());
|
||||||
|
if at_line_end && editor.row() == editor.num_lines() {
|
||||||
|
break
|
||||||
|
} else if at_line_end {
|
||||||
|
editor.delete_text(editor.row() + 1, 1).unwrap();
|
||||||
|
full_render = true;
|
||||||
|
} else {
|
||||||
|
editor.delete_text(editor.row(), editor.col() + 1).unwrap();
|
||||||
|
write!(stdout, "{}{}", termion::clear::CurrentLine, editor.text_line(editor.row())).unwrap();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Key::Home => editor.goto_col(0),
|
||||||
|
Key::End => editor.goto_last_col(),
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
} else if self.mode == 3 {
|
||||||
|
// Insert mode
|
||||||
|
match key {
|
||||||
|
Key::Char(c) => {
|
||||||
|
if c == '\n' {
|
||||||
|
editor.add_text(c.to_string());
|
||||||
|
full_render = true;
|
||||||
|
} else {
|
||||||
|
editor.add_text(c.to_string());
|
||||||
|
write!(stdout, "{}{}", "\r", editor.text_line(editor.row())).unwrap();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Key::Left => editor.left(1),
|
||||||
|
Key::Down => editor.down(1),
|
||||||
|
Key::Up => editor.up(1),
|
||||||
|
Key::Right => editor.right(1),
|
||||||
|
Key::Backspace => {
|
||||||
|
if editor.row() == 1 && editor.col() == 1 {
|
||||||
|
|
||||||
|
} else if editor.col() == 1 {
|
||||||
|
editor.up(1);
|
||||||
|
editor.goto_last_col();
|
||||||
|
editor.delete_text(editor.row() + 1, 1).unwrap();
|
||||||
|
full_render = true;
|
||||||
|
} else {
|
||||||
|
editor.left(1);
|
||||||
|
editor.delete_text(editor.row(), editor.col() + 1).unwrap();
|
||||||
|
write!(stdout, "{}{}{}", termion::clear::CurrentLine, termion::cursor::Goto(1, (editor.row() - visual_first_row + 1).try_into().unwrap()), editor.text_line(editor.row())).unwrap();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Key::Delete => {
|
||||||
|
let at_line_end = editor.col() == editor.num_cols(editor.row());
|
||||||
|
if at_line_end && editor.row() == editor.num_lines() {
|
||||||
|
break
|
||||||
|
} else if at_line_end {
|
||||||
|
editor.delete_text(editor.row() + 1, 1).unwrap();
|
||||||
|
full_render = true;
|
||||||
|
} else {
|
||||||
|
editor.delete_text(editor.row(), editor.col() + 1).unwrap();
|
||||||
|
write!(stdout, "{}{}", termion::clear::CurrentLine, editor.text_line(editor.row())).unwrap();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Key::Home => editor.goto_col(0),
|
||||||
|
Key::End => editor.goto_last_col(),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
} else if self.mode == 4 {
|
||||||
|
// Command line mode
|
||||||
|
match key {
|
||||||
|
Key::Char('\n') => {
|
||||||
|
let mut write = false;
|
||||||
|
let mut skip_write = false;
|
||||||
|
let mut quit = false;
|
||||||
|
match self.cmd_editor.text() {
|
||||||
|
":wq" | "x" => {write = true; quit = true;},
|
||||||
|
":q" => quit = true,
|
||||||
|
":q!" => {skip_write = true; quit = true}
|
||||||
|
text => {
|
||||||
|
// Don't panic, but output an error message
|
||||||
|
panic!(text.to_string())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if write {
|
||||||
|
if editor.file_path() == "" {
|
||||||
|
// Don't panic, but output an error message
|
||||||
|
panic!("FIXME")
|
||||||
|
} else {
|
||||||
|
File::create(editor.file_path()).unwrap().write_all(editor.text().as_bytes()).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if quit {
|
||||||
|
if editor.text_matches() {
|
||||||
|
write!(stdout, "{}", termion::cursor::BlinkingBlock).unwrap();
|
||||||
|
break
|
||||||
|
} else if skip_write {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// Don't panic, but output an error message
|
||||||
|
panic!("Quit without saving")
|
||||||
|
}
|
||||||
|
panic!("Invalid command")
|
||||||
|
}
|
||||||
|
Key::Char(c) => {
|
||||||
|
self.cmd_editor.add_text(c.to_string());
|
||||||
|
},
|
||||||
|
Key::Left => self.cmd_editor.left(1),
|
||||||
|
Key::Right => self.cmd_editor.right(1),
|
||||||
|
Key::Backspace => {
|
||||||
|
if self.cmd_editor.col() != 1 {
|
||||||
|
self.cmd_editor.left(1);
|
||||||
|
self.cmd_editor.delete_text(self.cmd_editor.row(), self.cmd_editor.col() + 1).unwrap();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Key::Delete => {
|
||||||
|
let at_line_end = self.cmd_editor.col() == self.cmd_editor.num_cols(self.cmd_editor.row());
|
||||||
|
if !at_line_end {
|
||||||
|
self.cmd_editor.delete_text(self.cmd_editor.row(), self.cmd_editor.col() + 1).unwrap();
|
||||||
|
write!(stdout, "{}{}", termion::clear::CurrentLine, self.cmd_editor.text_line(self.cmd_editor.row())).unwrap();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Key::Home => self.cmd_editor.goto_col(0),
|
||||||
|
Key::End => self.cmd_editor.goto_last_col(),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("Mode {} not implemented yet", self.mode);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Event::Mouse(me) => {
|
||||||
|
match me {
|
||||||
|
MouseEvent::Press(_, x, y) => {
|
||||||
|
editor.goto(y.try_into().unwrap(), min(x.try_into().unwrap(), editor.line_len(editor.row())));
|
||||||
|
self.mode = 0;
|
||||||
|
},
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
if full_render {
|
||||||
|
// write!(stdout, "{}{}", termion::clear::All, termion::cursor::Goto(1, 1)).unwrap();
|
||||||
|
for (i, line) in editor.text_lines(visual_first_row, visual_first_row + min(editor.num_lines(), term_rows)).lines().enumerate() {
|
||||||
|
write!(stdout, "{}{}", termion::cursor::Goto(1, (i + 1).try_into().unwrap()), line).unwrap();
|
||||||
|
}
|
||||||
|
full_render = false;
|
||||||
|
}
|
||||||
|
write!(stdout, "{}", termion::cursor::Goto(editor.col().try_into().unwrap(), (editor.row() - visual_first_row + 1).try_into().unwrap())).unwrap();
|
||||||
|
if self.mode == 0 {
|
||||||
|
write!(stdout, "{}", termion::cursor::BlinkingBlock).unwrap();
|
||||||
|
} else {
|
||||||
|
write!(stdout, "{}", termion::cursor::BlinkingBar).unwrap();
|
||||||
|
}
|
||||||
|
stdout.flush().unwrap();
|
||||||
}
|
}
|
||||||
let mut reader = BufReader::new(file);
|
write!(stdout, "{}{}{}{}", termion::clear::All, termion::cursor::Goto(1, 1), termion::cursor::Show, termion::cursor::BlinkingBlock).unwrap();
|
||||||
// Read until viewport is filled
|
|
||||||
// For now, only read 2 lines
|
|
||||||
let mut initial_text =String::new();
|
|
||||||
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::PieceTable::new(initial_text), reader, eof_reached, editor_options)
|
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
}
|
/// Process command line options and returns the files to edit and ViaOptions
|
||||||
|
// fn process_args(&mut self, args: &Vec<String>) -> Result<(Vec<String>, ViaOptions), &str> {
|
||||||
/// Process command line options and returns the files to edit and ViaOptions
|
pub(crate) fn process_args(args: Vec<String>) -> (Vec<String>, ViaOptions) {
|
||||||
// fn process_args(&mut self, args: &Vec<String>) -> Result<(Vec<String>, ViaOptions), &str> {
|
|
||||||
fn process_args(args: Vec<String>) -> (Vec<String>, ViaOptions) {
|
|
||||||
let mut flags = Vec::new();
|
let mut flags = Vec::new();
|
||||||
let mut file_paths = Vec::new();
|
let mut file_paths = Vec::new();
|
||||||
let flags_regex = Regex::new(r"--?\w+").unwrap();
|
|
||||||
// let default_files = vec!["target/debug/via", "via"];
|
// let default_files = vec!["target/debug/via", "via"];
|
||||||
for arg in &args[1..] {
|
for arg in &args[1..] {
|
||||||
if flags_regex.is_match(&arg) {
|
if arg == "--" {
|
||||||
|
break
|
||||||
|
} else if arg.starts_with("-") {
|
||||||
flags.push(arg);
|
flags.push(arg);
|
||||||
} else {
|
} else {
|
||||||
file_paths.push((*arg).as_str().to_string());
|
file_paths.push((*arg).as_str().to_string());
|
||||||
|
@ -86,9 +322,10 @@ impl Via {
|
||||||
}
|
}
|
||||||
(file_paths, via_options)
|
(file_paths, via_options)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone)]
|
||||||
struct ViaOptions {
|
pub(crate) struct ViaOptions {
|
||||||
/// Level of verboseness
|
/// Level of verboseness
|
||||||
verboseness: usize,
|
verboseness: usize,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue