use gur::prelude::*; use gur::snapshot::Snapshot; use gur::ur::{Ur, UrBuilder}; use std::cmp; use std::fs::File; use std::io; use std::io::{BufRead, BufReader, BufWriter, Read, Write}; use std::path::PathBuf; #[derive(Clone, Default)] struct TextBuffer(Vec); impl TextBuffer { fn new<'a>(buffer: Vec) -> Ur<'a, Self, String> { UrBuilder::default().build(TextBuffer(buffer)) } fn get(&self) -> &[char] { &self.0 } fn insert(&mut self, pos: usize, text: &str) { let pos = cmp::max(0, cmp::min(pos, self.0.len())); self.0.splice(pos..pos, text.chars()); } fn delete(&mut self, pos: usize, count: usize) { let begin = cmp::max(0, cmp::min(pos, self.0.len())); let end = cmp::max(0, cmp::min(pos + count, self.0.len())); self.0.drain(begin..end); } } impl Snapshot for TextBuffer { type Snapshot = String; fn to_snapshot(&self) -> Self::Snapshot { self.0.iter().collect() } fn from_snapshot(snapshot: &Self::Snapshot) -> Self { Self(snapshot.chars().collect()) } } struct TextEditor<'a> { buffer: Ur<'a, TextBuffer, String>, cursor: usize, file: Option, } impl<'a> TextEditor<'a> { fn new() -> Self { Self { buffer: TextBuffer::new(Vec::new()), cursor: 0, file: None, } } fn open(&mut self, path: Option) -> io::Result<()> { if let Some(path) = path { let path = PathBuf::from(path); let mut s = String::new(); if let Ok(f) = File::open(&path) { io::BufReader::new(f).read_to_string(&mut s)?; } self.buffer = TextBuffer::new(s.chars().collect()); self.cursor = 0; self.file = Some(path); } else { *self = Self::new(); } self.set_cursor_pos(self.cursor); Ok(()) } fn save(&self, path: Option) -> io::Result<()> { let path = if let Some(_) = path { &path } else { &self.file }; if path.is_none() { return Ok(()); } let mut file = io::BufWriter::new(File::create(path.as_ref().unwrap())?); self.write(&mut file)?; Ok(()) } fn write(&self, writer: &mut W) -> io::Result<()> { let s: String = (*self.buffer).get().iter().collect(); writer.write_all(s.as_bytes())?; Ok(()) } fn print(&self, writer: &mut W) -> io::Result<()> { // write text befor the cursor let s: String = (*self.buffer).get()[0..self.cursor].iter().collect(); writer.write_all(s.as_bytes())?; // write the cursor write!(writer, "|")?; // write text after the cursor let s: String = (*self.buffer).get()[self.cursor..].iter().collect(); writer.write_all(s.as_bytes())?; Ok(()) } fn seek(&mut self, count: isize) { let pos = if 0 < count { self.cursor.saturating_add(count as usize) } else { self.cursor.saturating_sub(-count as usize) }; self.set_cursor_pos(pos); } fn insert(&mut self, text: String) { let pos = self.cursor; self.buffer.edit(move |mut buf| { buf.insert(pos, &text); buf }); self.set_cursor_pos(self.cursor); } fn delete(&mut self, count: usize) { let pos = self.cursor; self.buffer.edit(move |mut buf| { buf.delete(pos, count); buf }); self.set_cursor_pos(self.cursor); } fn undo(&mut self, count: usize) { for _ in 0..count { self.buffer.undo(); } self.set_cursor_pos(self.cursor); } fn redo(&mut self, count: usize) { for _ in 0..count { self.buffer.redo(); } self.set_cursor_pos(self.cursor); } fn set_cursor_pos(&mut self, pos: usize) { self.cursor = cmp::min(pos, (*self.buffer).get().len()); } } const COMMAND_HELP: &str = "COMMANDS :h | Print command help. :p | Print the current text buffer and cursor position. :s [COUNT] | Seek the cursor +COUNT characters. :i TEXT | Insert TEXT at the cursor position. :d [COUNT] | Delete COUNT bytes from the cursor. :u [COUNT] | Undo COUNT changes. :r [COUNT] | Redo COUNT changes. :o [FILE] | Open FILE. :w [FILE] | Save the current text buffer as FILE. :q | Quit the program."; fn main() -> Result<(), Box> { println!("Simple text editor.\n\n{}", COMMAND_HELP); let mut app = TextEditor::new(); let mut stdout = BufWriter::new(io::stdout().lock()); let mut stdin = BufReader::new(io::stdin().lock()); let mut command_str = String::new(); loop { command_str.clear(); write!(&mut stdout, ":")?; stdout.flush()?; if 0 == stdin.read_line(&mut command_str)? { return Ok(()); } match Command::parse(&command_str)? { Command::Help => writeln!(&mut stdout, "{}", COMMAND_HELP)?, Command::Print => { app.print(&mut stdout)?; write!(&mut stdout, "\n")?; stdout.flush()?; } Command::Open(srcpath) => { app.open(srcpath)?; } Command::Write(dstpath) => { app.save(dstpath)?; } Command::Quit => return Ok(()), Command::Seek(count) => app.seek(count), Command::Insert(text) => app.insert(text), Command::Delete(count) => app.delete(count), Command::Undo(count) => app.undo(count), Command::Redo(count) => app.redo(count), } } } enum Command { Help, Print, Seek(isize), Insert(String), Delete(usize), Undo(usize), Redo(usize), Write(Option), Open(Option), Quit, } impl Command { fn parse(command_str: &str) -> Result> { let command_str = command_str .trim_start_matches(Self::is_whitespace) .trim_end_matches(&['\r', '\n']); let (name, param) = if let Some((name, param)) = command_str.split_once(Self::is_whitespace) { (name, param) } else { (command_str, "") }; match name { "h" => Ok(Command::Help), "p" => Ok(Command::Print), "q" => Ok(Command::Quit), "s" => Self::parse_seek(param), "i" => Self::parse_insert(param), "d" => Self::parse_delete(param), "u" => Self::parse_undo(param), "r" => Self::parse_redo(param), "o" => Self::parse_open(param), "w" => Self::parse_write(param), _ => Ok(Command::Help), } } fn parse_seek(param: &str) -> Result> { let count = Self::parse_isize(param)?; Ok(Command::Seek(count.unwrap_or(0))) } fn parse_insert(param: &str) -> Result> { Ok(Command::Insert(param.to_string())) } fn parse_delete(param: &str) -> Result> { let count = Self::parse_usize(param)?; Ok(Command::Delete(count.unwrap_or(1))) } fn parse_undo(param: &str) -> Result> { let count = Self::parse_usize(param)?; Ok(Command::Undo(count.unwrap_or(1))) } fn parse_redo(param: &str) -> Result> { let count = Self::parse_usize(param)?; Ok(Command::Redo(count.unwrap_or(1))) } fn parse_open(param: &str) -> Result> { Ok(Command::Open(Self::parse_path(param)?)) } fn parse_write(param: &str) -> Result> { Ok(Command::Write(Self::parse_path(param)?)) } fn parse_path(param: &str) -> Result, Box> { let path = if let Some(_) = param.find(|c: char| !Self::is_whitespace(c)) { Some(PathBuf::from(param)) } else { None }; Ok(path) } fn parse_isize(param: &str) -> Result, Box> { if let None = param.find(|c: char| !Self::is_whitespace(c)) { return Ok(None); } let num = param.parse::()?; Ok(Some(num)) } fn parse_usize(param: &str) -> Result, Box> { if let None = param.find(|c: char| !Self::is_whitespace(c)) { return Ok(None); } let num = param.parse::()?; Ok(Some(num)) } fn is_whitespace(c: char) -> bool { c.is_ascii_whitespace() } }