Compare commits
3 Commits
36ab1b1769
...
acd456c22a
| Author | SHA1 | Date |
|---|---|---|
|
|
acd456c22a | |
|
|
1634e64ebf | |
|
|
304bf4da6c |
|
|
@ -0,0 +1,84 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use super::{CommandResponse, EditorBuffer};
|
||||
use crate::{Point, TextBuffer, TextBufferWriter};
|
||||
|
||||
impl EditorBuffer {
|
||||
pub fn open_file(&mut self, filepath: PathBuf) -> CommandResponse {
|
||||
match std::fs::File::open(&filepath) {
|
||||
Ok(mut file) => {
|
||||
let mut buffer = TextBuffer::new();
|
||||
match std::io::copy(&mut file, &mut TextBufferWriter::new(&mut buffer)) {
|
||||
Ok(bytes_read) => {
|
||||
let msg = format!(
|
||||
"Read {bytes_read} bytes from \"{}\"",
|
||||
filepath.to_string_lossy()
|
||||
);
|
||||
self.filepath = Some(filepath);
|
||||
self.cursor = Point::default();
|
||||
self.buffer = buffer;
|
||||
CommandResponse::Success(msg)
|
||||
}
|
||||
Err(err) => CommandResponse::Failure(format!("{}", err)),
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
if err.kind() == std::io::ErrorKind::NotFound {
|
||||
CommandResponse::Failure(format!(
|
||||
"File not found: \"{}\"",
|
||||
filepath.to_string_lossy()
|
||||
))
|
||||
} else {
|
||||
CommandResponse::Failure(format!("{}", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save_file(&mut self, _filepath: Option<PathBuf>) -> CommandResponse {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{Command, CommandResponse, TextBufferReader, EditorBuffer};
|
||||
use std::io::Read;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[test]
|
||||
fn load_file_loads_expected_contents() {
|
||||
let test_file_path: PathBuf = [
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
r"test_data/Les_Trois_Mousquetaires.txt",
|
||||
]
|
||||
.iter()
|
||||
.collect();
|
||||
let expected_text = std::fs::read_to_string(&test_file_path).unwrap();
|
||||
let mut target = EditorBuffer::new();
|
||||
assert!(matches!(
|
||||
target.execute(Command::OpenFile(test_file_path)),
|
||||
CommandResponse::Success(_)
|
||||
));
|
||||
|
||||
let mut buffer_bytes = Vec::new();
|
||||
TextBufferReader::new(&target.buffer)
|
||||
.read_to_end(&mut buffer_bytes)
|
||||
.unwrap();
|
||||
let buffer_text = String::from_utf8(buffer_bytes).unwrap();
|
||||
assert_eq!(&expected_text, &buffer_text);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_file_reports_error_with_missing_file() {
|
||||
let test_file_path: PathBuf = [env!("CARGO_MANIFEST_DIR"), r"test_data/Not_a_File.txt"]
|
||||
.iter()
|
||||
.collect();
|
||||
let expected_message = format!("File not found: \"{}\"", test_file_path.to_string_lossy());
|
||||
let mut target = EditorBuffer::new();
|
||||
match target.execute(Command::OpenFile(test_file_path)) {
|
||||
CommandResponse::Failure(s) => assert_eq!(expected_message, s),
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use crate::{Point, TextBuffer};
|
||||
use crate::{Point, TextBuffer, TextBufferWriter};
|
||||
|
||||
mod io;
|
||||
mod command;
|
||||
pub use command::{Command, Movement, Unit};
|
||||
|
||||
|
|
@ -12,7 +13,11 @@ pub struct EditorBuffer {
|
|||
filepath: Option<PathBuf>,
|
||||
}
|
||||
|
||||
pub enum CommandResponse {}
|
||||
#[derive(Debug)]
|
||||
pub enum CommandResponse {
|
||||
Success(String),
|
||||
Failure(String),
|
||||
}
|
||||
|
||||
impl EditorBuffer {
|
||||
/// Create new empty [EditorBuffer]
|
||||
|
|
@ -35,14 +40,6 @@ impl EditorBuffer {
|
|||
}
|
||||
}
|
||||
|
||||
fn open_file(&mut self, _filepath: PathBuf) -> CommandResponse {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn save_file(&mut self, _filepath: Option<PathBuf>) -> CommandResponse {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn move_cursor_to_point(&mut self, _point: Point) -> CommandResponse {
|
||||
todo!()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
mod text_buffer;
|
||||
pub use text_buffer::{Point, TextBuffer, TextBufferReader, TextBufferWriter};
|
||||
mod editor_buffer;
|
||||
pub use editor_buffer::{Command,EditorBuffer};
|
||||
pub use editor_buffer::{Command, CommandResponse, EditorBuffer};
|
||||
|
|
|
|||
|
|
@ -1,25 +1,50 @@
|
|||
use super::{Point, TextBuffer};
|
||||
|
||||
pub struct TextBufferWriter<'a> {
|
||||
text_buffer: &'a mut TextBuffer,
|
||||
/// Stores any partial multi-byte characters that are left over from the
|
||||
/// last call to `write()`.
|
||||
overflow: Vec<u8>,
|
||||
}
|
||||
|
||||
impl<'a> TextBufferWriter<'a> {
|
||||
pub fn new(text_buffer: &'a mut TextBuffer) -> Self {
|
||||
Self { text_buffer }
|
||||
Self {
|
||||
text_buffer,
|
||||
overflow: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> std::io::Write for TextBufferWriter<'a> {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
self.text_buffer.insert_text(
|
||||
str::from_utf8(buf).map_err(std::io::Error::other)?,
|
||||
Point::End,
|
||||
);
|
||||
// If we get a UTF-8 decoding error, try backing off up to three bytes.
|
||||
// We might be in the middle of a multipart character and in that case
|
||||
// we should store the partial character in `overflow`. Not the most
|
||||
// efficient way to do this, ideally I should write some way to decode
|
||||
// text one character at a time.
|
||||
let bytes = if !self.overflow.is_empty() {
|
||||
self.overflow.extend_from_slice(buf);
|
||||
&self.overflow
|
||||
} else {
|
||||
buf
|
||||
};
|
||||
let text = str::from_utf8(bytes)
|
||||
.or_else(|_| str::from_utf8(&bytes[0..bytes.len() - 1]))
|
||||
.or_else(|_| str::from_utf8(&bytes[0..bytes.len() - 2]))
|
||||
.or_else(|_| str::from_utf8(&bytes[0..bytes.len() - 3]))
|
||||
.map_err(std::io::Error::other)?;
|
||||
self.text_buffer.insert_text(text, Point::End);
|
||||
self.overflow = bytes[text.len()..bytes.len()].to_vec();
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
if !self.overflow.is_empty() {
|
||||
self.text_buffer.insert_text(
|
||||
str::from_utf8(&self.overflow).map_err(std::io::Error::other)?,
|
||||
Point::End,
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue