diff --git a/src/app/context.rs b/src/app/context.rs index dcfe124..680f780 100644 --- a/src/app/context.rs +++ b/src/app/context.rs @@ -9,5 +9,6 @@ pub struct EditorContext { pub file_path: Option, pub current_message: Option, pub savedialog: bool, - pub quit_after_save: bool + pub quit_after_save: bool, + pub viewport_top: usize } diff --git a/src/commands/executor.rs b/src/commands/executor.rs index 492cbc3..2a4627d 100644 --- a/src/commands/executor.rs +++ b/src/commands/executor.rs @@ -1,20 +1,14 @@ use std::{ io::stdout, - path::{self, Path, PathBuf}, + path::{PathBuf}, }; use crossterm::{ execute, - terminal::{self, LeaveAlternateScreen}, }; use crate::{Buffer, Status}; use crate::{ - app::{ - context::{self, EditorContext}, - messages::MessageLevel, - }, - gui::status_bar::send_message, io::save::save, }; @@ -22,8 +16,8 @@ use super::parser::Command; pub fn execute( command: Command, - buffer: &Buffer, - path: &Option, + buffer: &mut Buffer, + path: &mut Option, ) -> std::io::Result { match command { Command::Quit => Ok(Status::Quit), diff --git a/src/editor/buffer.rs b/src/editor/buffer.rs index 1590249..028ca9a 100644 --- a/src/editor/buffer.rs +++ b/src/editor/buffer.rs @@ -1,10 +1,13 @@ use std::{fs::read_to_string, io::ErrorKind}; +use crate::editor::buffer; + #[derive(Debug)] pub struct Buffer { pub lines: Vec, pub row: usize, pub column: usize, + pub modified: bool, } pub fn fetch(file: &str) -> Result { @@ -35,5 +38,70 @@ pub fn fetch(file: &str) -> Result { lines: result, row: 0, column: 0, + modified: false, }) } + +impl Buffer { + pub fn line(&self, index: usize) -> Option<&str> { + self.lines.get(index).map(|line| line.as_str()) + } + + pub fn line_mut(&mut self, index: usize) -> Option<&mut String> { + self.lines.get_mut(index) + } + + pub fn line_count(&self) -> usize { + self.lines.len() + } + + pub fn char_at(&self, row: usize, column: usize) -> Option { + let Some(line) = self.line(column) else { + return None; + }; + + line.chars().nth(row) + } + + pub fn insert_char_at(&mut self, row: usize, column: usize, c: char) -> bool { + let Some(line) = self.line_mut(column) else { + return false; + }; + let byte_index = line + .char_indices() + .nth(row) + .map(|(index, _)| index) + .unwrap_or(line.len()); + line.insert(byte_index, c); + + true + } + + pub fn char_count(&self, row: usize) -> Option { + let Some(line) = self.line(row) else { + return None; + }; + + return Some(line.chars().count()); + } + + pub fn remove_char_at(&mut self, row: usize, column: usize) -> Option { + let Some(line) = self.line_mut(column) else { + return None; + }; + + let Some(index) = line.char_indices().nth(row).map(|(index, _)| index) else { + return None; + }; + + Some(line.remove(index)) + } + + pub fn mark_modified(&mut self) { + self.modified = true; + } + + pub fn mark_saved(&mut self) { + self.modified = false; + } +} diff --git a/src/editor/editing.rs b/src/editor/editing.rs index cff1d32..ec97e96 100644 --- a/src/editor/editing.rs +++ b/src/editor/editing.rs @@ -3,20 +3,17 @@ use crate::app::mode::Mode; use crate::editor::buffer::Buffer; pub fn insert_char(c: char, buffer: &mut Buffer) { - let Some(line) = buffer.lines.get_mut(buffer.column) else { + let modified = buffer.insert_char_at(buffer.row, buffer.column, c); + if modified { + buffer.row += 1; + buffer.mark_modified(); + } else { return; - }; - let byte_index = line - .char_indices() - .nth(buffer.row) - .map(|(index, _)| index) - .unwrap_or(line.len()); - line.insert(byte_index, c); - buffer.row += 1; + } } pub fn insert_forward(buffer: &mut Buffer, context: &mut EditorContext) { - let line_len = buffer.lines[buffer.column].chars().count(); + let line_len = buffer.char_count(buffer.column).unwrap_or(0); if line_len > 0 && buffer.row < line_len { buffer.row += 1; @@ -35,26 +32,23 @@ pub fn insert_backward(buffer: &mut Buffer, context: &mut EditorContext) { pub fn remove_char(buffer: &mut Buffer) { if buffer.row > 0 { - let Some(line) = buffer.lines.get_mut(buffer.column) else { + let modified = buffer.remove_char_at(buffer.row - 1, buffer.column); + if modified.is_some() { + buffer.row -= 1; + buffer.mark_modified(); + } else { return; - }; - - let Some(index) = line - .char_indices() - .nth(buffer.row - 1) - .map(|(index, _)| index) - else { - return - }; - - line.remove(index); - buffer.row -= 1; + } } else if buffer.column > 0 { - let current_line = buffer.lines[buffer.column].clone(); - let previous_len = buffer.lines[buffer.column - 1].chars().count(); + let Some(current_line) = buffer.line(buffer.column) else { + return; + }; + let current_line = current_line.to_string(); + + let previous_len = buffer.char_count(buffer.column - 1).unwrap_or(0); { - let Some(previous) = buffer.lines.get_mut(buffer.column - 1) else { + let Some(previous) = buffer.line_mut(buffer.column - 1) else { return; }; @@ -64,18 +58,20 @@ pub fn remove_char(buffer: &mut Buffer) { buffer.lines.remove(buffer.column); buffer.column -= 1; buffer.row = previous_len; + buffer.mark_modified(); } } pub fn insert_newline(buffer: &mut Buffer) { + let row = buffer.row; let rest = { - let Some(line) = buffer.lines.get_mut(buffer.column) else { + let Some(line) = buffer.line_mut(buffer.column) else { return; }; let index = line .char_indices() - .nth(buffer.row) + .nth(row) .map(|(index, _)| index) .unwrap_or(line.len()); @@ -85,4 +81,5 @@ pub fn insert_newline(buffer: &mut Buffer) { buffer.lines.insert(buffer.column + 1, rest); buffer.column += 1; buffer.row = 0; + buffer.mark_modified(); } diff --git a/src/editor/movement.rs b/src/editor/movement.rs index 840451a..26fbe20 100644 --- a/src/editor/movement.rs +++ b/src/editor/movement.rs @@ -8,11 +8,7 @@ enum CharType { } pub fn move_right(buffer: &mut Buffer) { - let Some(line) = buffer.lines.get(buffer.column) else { - return; - }; - - let len = line.chars().count(); + let len = buffer.char_count(buffer.column).unwrap_or(0); if len == 0 || buffer.row >= len - 1 { return; @@ -30,7 +26,7 @@ pub fn move_left(lines: &mut Buffer) { } pub fn move_down(lines: &mut Buffer) { - let len = lines.lines.len(); + let len = lines.line_count(); if len == 0 || lines.column >= len - 1 { return; @@ -38,7 +34,7 @@ pub fn move_down(lines: &mut Buffer) { lines.column += 1; - let line_len = lines.lines[lines.column].chars().count(); + let line_len = lines.char_count(lines.column).unwrap_or(0); if line_len == 0 { lines.row = 0; @@ -54,7 +50,7 @@ pub fn move_up(lines: &mut Buffer) { lines.column -= 1; - let line_len = lines.lines[lines.column].chars().count(); + let line_len = lines.char_count(lines.column).unwrap_or(0); if line_len == 0 { lines.row = 0; @@ -68,7 +64,7 @@ pub fn move_tostart(buffer: &mut Buffer) { } pub fn move_toend(buffer: &mut Buffer) { - let line_len = buffer.lines[buffer.column].chars().count(); + let line_len = buffer.char_count(buffer.column).unwrap_or(0); if line_len == 0 { return; @@ -78,11 +74,14 @@ pub fn move_toend(buffer: &mut Buffer) { } pub fn move_tonext(buffer: &mut Buffer) { - if buffer.column >= buffer.lines.len() { + if buffer.column >= buffer.line_count() { return; } - let line: Vec = buffer.lines[buffer.column].chars().collect(); + let Some(current_line) = buffer.line(buffer.column) else { + return; + }; + let line: Vec = current_line.chars().collect(); if line.is_empty() || buffer.row >= line.len() { return; @@ -106,8 +105,11 @@ pub fn move_tonext(buffer: &mut Buffer) { let mut next_column = buffer.column + 1; - while next_column < buffer.lines.len() { - let next_line: Vec = buffer.lines[next_column].chars().collect(); + while next_column < buffer.line_count() { + let Some(next_str) = buffer.line(next_column) else { + break; + }; + let next_line: Vec = next_str.chars().collect(); if let Some(pos) = next_line .iter() @@ -125,22 +127,25 @@ pub fn move_tonext(buffer: &mut Buffer) { } pub fn move_toprev(buffer: &mut Buffer) { - if buffer.lines.is_empty() { + if buffer.line_count() == 0 { return; } if buffer.row == 0 { if buffer.column > 0 { buffer.column -= 1; - buffer.row = buffer.lines[buffer.column] - .chars() - .count() + buffer.row = buffer + .char_count(buffer.column) + .unwrap_or(0) .saturating_sub(1); } return; } - let line: Vec = buffer.lines[buffer.column].chars().collect(); + let Some(current_line) = buffer.line(buffer.column) else { + return; + }; + let line: Vec = current_line.chars().collect(); while buffer.row > 0 && char_type(line[buffer.row - 1]) == CharType::Whitespace { buffer.row -= 1; @@ -159,19 +164,22 @@ pub fn move_toprev(buffer: &mut Buffer) { } pub fn move_to_next_word(buffer: &mut Buffer) { - if buffer.lines.is_empty() { + if buffer.line_count() == 0 { return; } - if buffer.column >= buffer.lines.len() { + if buffer.column >= buffer.line_count() { return; } let mut col = buffer.column; let mut pos = buffer.row; - while col < buffer.lines.len() { - let line: Vec = buffer.lines[col].chars().collect(); + while col < buffer.line_count() { + let Some(current_line) = buffer.line(col) else { + break; + }; + let line: Vec = current_line.chars().collect(); if line.is_empty() || pos >= line.len() { col += 1; @@ -215,7 +223,7 @@ pub fn move_to_next_word(buffer: &mut Buffer) { } pub fn moveto_prev_word_end(buffer: &mut Buffer) { - if buffer.lines.is_empty() || buffer.column >= buffer.lines.len() { + if buffer.line_count() == 0 || buffer.column >= buffer.line_count() { return; } @@ -225,7 +233,9 @@ pub fn moveto_prev_word_end(buffer: &mut Buffer) { let mut col = original_col; let mut pos = original_row; - let current_line: Vec = buffer.lines[col].chars().collect(); + let current_line: Vec = buffer + .line(col) + .map_or_else(Vec::new, |l| l.chars().collect()); if pos < current_line.len() && char_type(current_line[pos]) == CharType::Word { while pos > 0 && char_type(current_line[pos - 1]) == CharType::Word { @@ -241,7 +251,9 @@ pub fn moveto_prev_word_end(buffer: &mut Buffer) { col -= 1; - let line: Vec = buffer.lines[col].chars().collect(); + let line: Vec = buffer + .line(col) + .map_or_else(Vec::new, |l| l.chars().collect()); pos = line.len(); if pos == 0 { @@ -249,7 +261,9 @@ pub fn moveto_prev_word_end(buffer: &mut Buffer) { } } - let line: Vec = buffer.lines[col].chars().collect(); + let line: Vec = buffer + .line(col) + .map_or_else(Vec::new, |l| l.chars().collect()); while pos > 0 && char_type(line[pos - 1]) != CharType::Word { pos -= 1; @@ -272,11 +286,11 @@ pub fn move_to_start(buffer: &mut Buffer) { } pub fn move_to_end(buffer: &mut Buffer) { - if buffer.lines.is_empty() { + if buffer.line_count() == 0 { return; } else { buffer.row = 0; - buffer.column = buffer.lines.len().saturating_sub(1); + buffer.column = buffer.line_count().saturating_sub(1); } } diff --git a/src/gui/display.rs b/src/gui/display.rs index fa6d300..f383b64 100644 --- a/src/gui/display.rs +++ b/src/gui/display.rs @@ -1,5 +1,5 @@ use crate::{ - app::context::EditorContext, app::mode::Mode, editor::buffer::Buffer, explorer::tree::Explorer, + OpenBuffer, app::{context::EditorContext, mode::Mode}, editor::buffer::Buffer, explorer::tree::Explorer }; use crossterm::{ @@ -22,6 +22,7 @@ pub(super) enum Colors { GREEN, BLUE, CYAN, + YELLOW } pub(super) struct Cell { @@ -33,7 +34,7 @@ pub(super) struct Cell { pub struct Display { pub(super) cells: Vec, pub(super) width: u32, - pub(super) height: u32, + pub height: u32, } pub(super) fn terminal_to_color(color: &Colors) -> Color { @@ -44,6 +45,7 @@ pub(super) fn terminal_to_color(color: &Colors) -> Color { Colors::GREEN => Color::Green, Colors::BLUE => Color::Blue, Colors::CYAN => Color::Cyan, + Colors::YELLOW => Color::Yellow } } @@ -78,6 +80,7 @@ impl Display { context: &EditorContext, explorer: &Explorer, sidebar_visible: bool, + buffers: &[OpenBuffer] ) -> std::io::Result<()> { let mut stdout = stdout(); @@ -89,10 +92,10 @@ impl Display { let editor_offset = if sidebar_visible { 26 } else { 0 }; - editor_view::draw_editor(self, buffer, editor_offset); + editor_view::draw_editor(self, buffer, editor_offset, context); if sidebar_visible { - explorer_view::draw_explorer(self, explorer); + explorer_view::draw_explorer(self, explorer, context, buffers); } status_bar::draw_status_bar(self, context); @@ -135,7 +138,10 @@ impl Display { _ => { queue!( stdout, - MoveTo(buffer.row as u16, buffer.column as u16), + MoveTo( + (editor_offset + buffer.row) as u16, + buffer.column.saturating_sub(context.viewport_top) as u16, + ), Show )?; } diff --git a/src/gui/editor_view.rs b/src/gui/editor_view.rs index 60b0317..d1e234b 100644 --- a/src/gui/editor_view.rs +++ b/src/gui/editor_view.rs @@ -1,4 +1,4 @@ -use crate::editor::buffer::Buffer; +use crate::{app::context::EditorContext, editor::buffer::Buffer}; use super::display::Display; @@ -6,17 +6,21 @@ pub(super) fn draw_editor( display: &mut Display, buffer: &Buffer, editor_offset: usize, + context: &EditorContext, ) { let width = display.width as usize; let height = display.height as usize; + let viewport = context.viewport_top; let editor_height = height.saturating_sub(1); - for (y, line) in buffer.lines.iter().enumerate() { - if y >= editor_height { - break; - } - + for (y, line) in buffer + .lines + .iter() + .skip(viewport) + .take(editor_height) + .enumerate() + { for (x, ch) in line.chars().enumerate() { let screen_x = editor_offset + x; @@ -31,4 +35,16 @@ pub(super) fn draw_editor( } } } -} \ No newline at end of file +} + +pub fn update_viewport(buffer: &Buffer, context: &mut EditorContext, height: usize) { + if height == 0 { + return; + } + if buffer.column < context.viewport_top { + context.viewport_top = buffer.column; + } + if buffer.column >= context.viewport_top + height { + context.viewport_top = buffer.column - height + 1; + } +} diff --git a/src/gui/explorer_view.rs b/src/gui/explorer_view.rs index 7bcc77a..0511f34 100644 --- a/src/gui/explorer_view.rs +++ b/src/gui/explorer_view.rs @@ -1,10 +1,12 @@ -use crate::explorer::tree::Explorer; +use crate::{app::context::EditorContext, OpenBuffer, explorer::tree::Explorer}; use super::display::{Colors, Display}; pub(super) fn draw_explorer( display: &mut Display, explorer: &Explorer, + context: &EditorContext, + buffers: &[OpenBuffer], ) { let width = display.width as usize; let height = display.height as usize; @@ -15,6 +17,11 @@ pub(super) fn draw_explorer( break; } + let is_modified = buffers.iter().any(|open_buffer| { + open_buffer.path.as_ref() == Some(&entry.path) + && open_buffer.buffer.modified + }); + for x in 0..sidebar_width { if x >= width { break; @@ -26,6 +33,12 @@ pub(super) fn draw_explorer( if let Some(cell) = display.cells.get_mut(cell_index) { cell.symbol = ch; + if is_modified { + cell.fg = Colors::YELLOW + } else { + cell.fg = Colors::WHITE; + } + if y == explorer.selected { cell.bg = Colors::BLUE; } else { @@ -47,4 +60,4 @@ pub(super) fn draw_explorer( } } } -} \ No newline at end of file +} diff --git a/src/io/save.rs b/src/io/save.rs index 067ac99..15107d5 100644 --- a/src/io/save.rs +++ b/src/io/save.rs @@ -2,10 +2,12 @@ use crate::editor::buffer::Buffer; use std::{fs::write, path::Path}; -pub fn save(buffer: &Buffer, path: &Path) -> std::io::Result<()> { +pub fn save(buffer: &mut Buffer, path: &Path) -> std::io::Result<()> { let content = buffer.lines.join("\n"); write(path, content)?; + buffer.mark_saved(); + Ok(()) } diff --git a/src/main.rs b/src/main.rs index ff86885..cc4686b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,23 @@ use std::{io::stdout, path::PathBuf, time::Instant}; use crate::{ - Status::Continue, app::{ + Status::Continue, + app::{ messages::MessageLevel, mode::{Mode, set_command, set_normal}, - }, commands::{executor, parser}, editor::{ - editing::{insert_backward, insert_newline, remove_char}, movement::{ - move_down, move_left, move_right, move_to_end, move_to_next_word, move_to_start, move_toend, move_tonext, move_toprev, move_tostart, move_up, moveto_prev_word_end, + }, + commands::{executor, parser}, + editor::{ + editing::{insert_backward, insert_newline, remove_char}, + movement::{ + move_down, move_left, move_right, move_to_end, move_to_next_word, move_to_start, + move_toend, move_tonext, move_toprev, move_tostart, move_up, moveto_prev_word_end, }, - }, explorer::tree::{Explorer, read_dir}, gui::{display::Display, status_bar::send_message}, input::keyboard::{PendingKey, keybinding}, io::save::save, + }, + explorer::tree::{Explorer, read_dir}, + gui::{display::Display, editor_view::update_viewport, status_bar::send_message}, + input::keyboard::{PendingKey, keybinding}, + io::save::save, }; use crossterm::{ event::{ @@ -42,9 +51,15 @@ mod input; mod io; pub struct App { - editor: buffer::Buffer, + buffers: Vec, explorer: Explorer, sidebar_visible: bool, + active_buffer: usize, +} + +pub struct OpenBuffer { + pub path: Option, + pub buffer: Buffer, } pub enum Status { @@ -60,13 +75,18 @@ fn main() -> std::io::Result<()> { let mut app = match &path { Some(path) => match Path::new(&path).is_dir() { true => App { - editor: Buffer { - lines: vec![String::new()], - row: 0, - column: 0, - }, + buffers: vec![OpenBuffer { + path: None, + buffer: Buffer { + lines: vec![String::new()], + row: 0, + column: 0, + modified: false, + }, + }], explorer: read_dir(&PathBuf::from(&path), 0), sidebar_visible: false, + active_buffer: 0, }, false => { let file_path = Path::new(&path); @@ -77,30 +97,40 @@ fn main() -> std::io::Result<()> { .unwrap_or(Path::new(".")); App { - editor: buffer::fetch(&path)?, + buffers: vec![OpenBuffer { + path: Some(file_path.to_path_buf()), + buffer: buffer::fetch(&path)?, + }], explorer: read_dir(&PathBuf::from(parent), 0), sidebar_visible: false, + active_buffer: 0, } } }, None => App { - editor: Buffer { - lines: vec![String::new()], - row: 0, - column: 0, - }, + buffers: vec![OpenBuffer { + path: None, + buffer: Buffer { + lines: vec![String::new()], + row: 0, + column: 0, + modified: false, + }, + }], explorer: read_dir(&PathBuf::from("."), 0), sidebar_visible: false, + active_buffer: 0, }, }; let mut context = EditorContext { mode: Mode::Normal, command_buffer: String::new(), - file_path: path.map(PathBuf::from), + file_path: app.buffers[app.active_buffer].path.clone(), current_message: None, savedialog: false, quit_after_save: false, + viewport_top: 0, }; terminal::enable_raw_mode()?; @@ -117,10 +147,11 @@ fn main() -> std::io::Result<()> { let mut pending_key = PendingKey::None; display.draw( - &mut app.editor, + &app.buffers[app.active_buffer].buffer, &context, &app.explorer, app.sidebar_visible, + &app.buffers )?; let mut needs_redraw = true; @@ -151,7 +182,14 @@ fn main() -> std::io::Result<()> { needs_redraw = true; } } - let status = manager(&mut queue, &mut app.editor, &mut context, &mut app.explorer)?; + let status = manager( + &mut queue, + &mut app.buffers, + &mut app.active_buffer, + &mut context, + &mut app.explorer, + (display.height.saturating_sub(1)) as usize, + )?; match status { Status::Continue => {} @@ -167,10 +205,11 @@ fn main() -> std::io::Result<()> { } if needs_redraw { display.draw( - &mut app.editor, + &app.buffers[app.active_buffer].buffer, &context, &app.explorer, app.sidebar_visible, + &app.buffers )?; needs_redraw = false; @@ -183,107 +222,133 @@ fn main() -> std::io::Result<()> { fn manager( queue: &mut VecDeque, - lines: &mut Buffer, + buffers: &mut Vec, + active_buffer: &mut usize, context: &mut EditorContext, explorer: &mut Explorer, + editor_height: usize, ) -> std::io::Result { let Some(first) = queue.pop_front() else { return Ok(Continue); }; + match context.mode { - Mode::Normal => match first { - Action::MoveLeft => move_left(lines), - Action::MoveRight => move_right(lines), - Action::MoveUp => move_up(lines), - Action::MoveDown => move_down(lines), - Action::InsertForward => insert_forward(lines, context), - Action::InserBackward => insert_backward(lines, context), - Action::SetCommand => { - context.current_message = None; - set_command(context); - } - Action::StartLine => move_tostart(lines), - Action::EndLine => move_toend(lines), - Action::MoveNext => move_tonext(lines), - Action::MovePrev => move_toprev(lines), - Action::MoveToPrevWordEnd => moveto_prev_word_end(lines), - Action::SetExplorer => { - return Ok(Status::OpenExplorer); + Mode::Normal => { + let lines = &mut buffers[*active_buffer].buffer; + match first { + Action::MoveLeft => move_left(lines), + Action::MoveRight => move_right(lines), + Action::MoveUp => move_up(lines), + Action::MoveDown => move_down(lines), + Action::InsertForward => insert_forward(lines, context), + Action::InserBackward => insert_backward(lines, context), + Action::SetCommand => { + context.current_message = None; + set_command(context); + } + Action::StartLine => move_tostart(lines), + Action::EndLine => move_toend(lines), + Action::MoveNext => move_tonext(lines), + Action::MovePrev => move_toprev(lines), + Action::MoveToPrevWordEnd => moveto_prev_word_end(lines), + Action::SetExplorer => { + return Ok(Status::OpenExplorer); + } + Action::MoveToWord => move_to_next_word(lines), + Action::MoveToStart => move_to_start(lines), + Action::MoveToEnd => move_to_end(lines), + _ => {} } - Action::MoveToWord => move_to_next_word(lines), - Action::MoveToStart => move_to_start(lines), - Action::MoveToEnd => move_to_end(lines), - _ => {} - }, - Mode::Insert => match first { - Action::InsertChar(char) => insert_char(char, lines), - Action::SetNormal => set_normal(context), - Action::RemoveChar => remove_char(lines), - Action::InserNewLine => insert_newline(lines), - _ => {} - }, - Mode::Command => match first { - Action::EscCommand => set_normal(context), - Action::InsertChar(char) => context.command_buffer.push(char), - Action::RemoveChar => { - context.command_buffer.pop(); + } + Mode::Insert => { + let lines = &mut buffers[*active_buffer].buffer; + match first { + Action::InsertChar(char) => insert_char(char, lines), + Action::SetNormal => set_normal(context), + Action::RemoveChar => remove_char(lines), + Action::InserNewLine => insert_newline(lines), + _ => {} } - Action::SendCommand => { - if context.savedialog { - let path = std::path::Path::new(&context.command_buffer); - - match save(lines, path) { - Ok(()) => { - context.file_path = Some(path.to_path_buf()); - context.command_buffer.clear(); - context.savedialog = false; - set_normal(context); - - if context.quit_after_save { - context.quit_after_save = false; - return Ok(Status::Quit); - } + } + Mode::Command => { + let lines = &mut buffers[*active_buffer].buffer; + match first { + Action::EscCommand => set_normal(context), + Action::InsertChar(char) => context.command_buffer.push(char), + Action::RemoveChar => { + context.command_buffer.pop(); + } + Action::SendCommand => { + if context.savedialog { + let path = std::path::Path::new(&context.command_buffer); - send_message(context, "File Saved.".to_string(), MessageLevel::Success); - return Ok(Status::Continue); - } - Err(error) => { - send_message( - context, - format!("Cannot save file: {error}"), - MessageLevel::Error, - ); - return Ok(Status::Continue); + match save(lines, path) { + Ok(()) => { + context.file_path = Some(path.to_path_buf()); + buffers[*active_buffer].path = Some(path.to_path_buf()); + context.command_buffer.clear(); + context.savedialog = false; + set_normal(context); + + if context.quit_after_save { + context.quit_after_save = false; + return Ok(Status::Quit); + } + send_message( + context, + "File Saved.".to_string(), + MessageLevel::Success, + ); + return Ok(Status::Continue); + } + Err(error) => { + send_message( + context, + format!("Cannot save file: {error}"), + MessageLevel::Error, + ); + return Ok(Status::Continue); + } } } - } - let command = parser::parse(context.command_buffer.as_str()); - let needs_filename = matches!( - &command, - parser::Command::Write(None) | parser::Command::WriteQuit(None) - ); + let command = parser::parse(context.command_buffer.as_str()); + let needs_filename = matches!( + &command, + parser::Command::Write(None) | parser::Command::WriteQuit(None) + ); - if needs_filename && context.file_path.is_none() { - context.quit_after_save = matches!(&command, parser::Command::WriteQuit(None)); + if needs_filename && context.file_path.is_none() { + context.quit_after_save = + matches!(&command, parser::Command::WriteQuit(None)); - context.savedialog = true; - context.command_buffer.clear(); - return Ok(Status::Continue); - } + context.savedialog = true; + context.command_buffer.clear(); + return Ok(Status::Continue); + } - let is_write = matches!(&command, parser::Command::Write(_)); - let status = executor::execute(command, lines, &context.file_path)?; + let is_write = matches!(&command, parser::Command::Write(_)); + let is_saving = matches!( + &command, + parser::Command::Write(_) | parser::Command::WriteQuit(_) + ); + let status = executor::execute(command, lines, &mut context.file_path)?; - if is_write { - set_normal(context); - send_message(context, "File Saved.".to_string(), MessageLevel::Success); - } + if is_saving { + buffers[*active_buffer].path = context.file_path.clone(); + } + + if is_write { + set_normal(context); + send_message(context, "File Saved.".to_string(), MessageLevel::Success); + buffers[*active_buffer].path = context.file_path.clone(); + } - return Ok(status); + return Ok(status); + } + _ => {} } - _ => {} - }, + } Mode::Explorer => match first { Action::SetNormal => { set_normal(context); @@ -297,23 +362,42 @@ fn manager( } else { let path = explorer.entries[explorer.selected].path.clone(); - match buffer::fetch(path.to_string_lossy().as_ref()) { - Ok(buffer) => { - *lines = buffer; - context.file_path = Some(path); - } - Err(error) => { - send_message( - context, - format!("Cannot open file: {error}"), - MessageLevel::Error, - ); + let existing = buffers + .iter() + .position(|open_buffer| open_buffer.path.as_ref() == Some(&path)); + + let opened = match existing { + Some(index) => { + *active_buffer = index; + true } + None => match buffer::fetch(path.to_string_lossy().as_ref()) { + Ok(buffer) => { + buffers.push(OpenBuffer { + path: Some(path.clone()), + buffer, + }); + *active_buffer = buffers.len() - 1; + true + } + Err(error) => { + send_message( + context, + format!("Cannot open file: {error}"), + MessageLevel::Error, + ); + false + } + }, + }; + if opened { + context.file_path = Some(path.clone()); } } } _ => {} }, } + update_viewport(&mut buffers[*active_buffer].buffer, context, editor_height); Ok(Continue) } diff --git a/src/test.txt b/src/test.txt index 862fa05..b449774 100644 --- a/src/test.txt +++ b/src/test.txt @@ -16,4 +16,381 @@ a a a a -a ciao boss \ No newline at end of file +aa ciao boss + + +hsadsassdajhgsawdscjghgdvsajhdcvciaoè aasqdjhsdavchgdsaciaociaociaociaoidscvbsdhgvdsajhgdscvhgdsvhi +hello hello +hello +test +test + +test +a +a +a +a +a +a + +a +a +a +a +aa ciao boss + + + +hsadsassdajhgsawdscjghgdvsajhdcvciaoè aasqdjhsdavchgdsaciaociaociaociaoidscvbsdhgvdsajhgdscvhgdsvhi +hello hello +hello +test +test + +test +a +a +a +a +a +a + +a +a +a +a +aa ciao boss + + + +hsadsassdajhgsawdscjghgdvsajhdcvciaoè aasqdjhsdavchgdsaciaociaociaociaoidscvbsdhgvdsajhgdscvhgdsvhi +hello hello +hello +test +test + +test +a +a +a +a +a +a + +a +a +a +a +aa ciao boss + + + +hsadsassdajhgsawdscjghgdvsajhdcvciaoè aasqdjhsdavchgdsaciaociaociaociaoidscvbsdhgvdsajhgdscvhgdsvhi +hello hello +hello +test +test + +test +a +a +a +a +a +a + +a +a +a +a +aa ciao boss + + + +hsadsassdajhgsawdscjghgdvsajhdcvciaoè aasqdjhsdavchgdsaciaociaociaociaoidscvbsdhgvdsajhgdscvhgdsvhi +hello hello +hello +test +test + +test +a +a +a +a +a +a + +a +a +a +a +aa ciao boss + + + +hsadsassdajhgsawdscjghgdvsajhdcvciaoè aasqdjhsdavchgdsaciaociaociaociaoidscvbsdhgvdsajhgdscvhgdsvhi +hello hello +hello +test +test + +test +a +a +a +a +a +a + +a +a +a +a +aa ciao boss + + + +hsadsassdajhgsawdscjghgdvsajhdcvciaoè aasqdjhsdavchgdsaciaociaociaociaoidscvbsdhgvdsajhgdscvhgdsvhi +hello hello +hello +test +test + +test +a +a +a +a +a +a + +a +a +a +a +aa ciao boss + + + +hsadsassdajhgsawdscjghgdvsajhdcvciaoè aasqdjhsdavchgdsaciaociaociaociaoidscvbsdhgvdsajhgdscvhgdsvhi +hello hello +hello +test +test + +test +a +a +a +a +a +a + +a +a +a +a +aa ciao boss + + + +hsadsassdajhgsawdscjghgdvsajhdcvciaoè aasqdjhsdavchgdsaciaociaociaociaoidscvbsdhgvdsajhgdscvhgdsvhi +hello hello +hello +test +test + +test +a +a +a +a +a +a + +a +a +a +a +aa ciao boss + + + +hsadsassdajhgsawdscjghgdvsajhdcvciaoè aasqdjhsdavchgdsaciaociaociaociaoidscvbsdhgvdsajhgdscvhgdsvhi +hello hello +hello +test +test + +test +a +a +a +a +a +a + +a +a +a +a +aa ciao boss + + + +hsadsassdajhgsawdscjghgdvsajhdcvciaoè aasqdjhsdavchgdsaciaociaociaociaoidscvbsdhgvdsajhgdscvhgdsvhi +hello hello +hello +test +test + +test +a +a +a +a +a +a + +a +a +a +a +aa ciao boss + + + +hsadsassdajhgsawdscjghgdvsajhdcvciaoè aasqdjhsdavchgdsaciaociaociaociaoidscvbsdhgvdsajhgdscvhgdsvhi +hello hello +hello +test +test + +test +a +a +a +a +a +a + +a +a +a +a +aa ciao boss + + + +hsadsassdajhgsawdscjghgdvsajhdcvciaoè aasqdjhsdavchgdsaciaociaociaociaoidscvbsdhgvdsajhgdscvhgdsvhi +hello hello +hello +test +test + +test +a +a +a +a +a +a + +a +a +a +a +aa ciao boss + + + +hsadsassdajhgsawdscjghgdvsajhdcvciaoè aasqdjhsdavchgdsaciaociaociaociaoidscvbsdhgvdsajhgdscvhgdsvhi +hello hello +hello +test +test + +test +a +a +a +a +a +a + +a +a +a +a +aa ciao boss + + + +hsadsassdajhgsawdscjghgdvsajhdcvciaoè aasqdjhsdavchgdsaciaociaociaociaoidscvbsdhgvdsajhgdscvhgdsvhi +hello hello +hello +test +test + +test +a +a +a +a +a +a + +a +a +a +a +aa ciao boss + + + +hsadsassdajhgsawdscjghgdvsajhdcvciaoè aasqdjhsdavchgdsaciaociaociaociaoidscvbsdhgvdsajhgdscvhgdsvhi +hello hello +hello +test +test + +test +a +a +a +a +a +a + +a +a +a +a +aa ciao boss + + + +hsadsassdajhgsawdscjghgdvsajhdcvciaoè aasqdjhsdavchgdsaciaociaociaociaoidscvbsdhgvdsajhgdscvhgdsvhi +hello hello +hello +test +test + +test +a +a +a +a +a +a + +a +a +a +a +aa ciao boss + + + +test \ No newline at end of file