diff --git a/src/clipboard.rs b/src/clipboard.rs deleted file mode 100644 index d9594b4..0000000 --- a/src/clipboard.rs +++ /dev/null @@ -1,42 +0,0 @@ -use std::{cell::RefCell, rc::Rc}; - -#[derive(Clone)] -pub struct Clipboard { - clipboard: Rc>, -} - -impl Clipboard { - pub fn new() -> Self { - Self { - clipboard: Rc::new(RefCell::new(Internal::new())), - } - } - - pub fn get(&self) -> String { - self.clipboard.borrow().get().to_owned() - } - - pub fn set(&mut self, text: String) { - self.clipboard.borrow_mut().set(text); - } -} - -struct Internal { - contents: String, -} - -impl Internal { - fn new() -> Self { - Self { - contents: String::new(), - } - } - - fn get(&self) -> &str { - &self.contents - } - - fn set(&mut self, text: String) { - self.contents = text; - } -} diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..99d3c7e --- /dev/null +++ b/src/config.rs @@ -0,0 +1,21 @@ +pub struct Config { + clipboard: String, + pub line_numbers: bool, +} + +impl Config { + pub fn new() -> Self { + Self { + clipboard: String::new(), + line_numbers: true, + } + } + + pub fn clipboard(&self) -> &str { + &self.clipboard + } + + pub fn set_clipboard(&mut self, text: String) { + self.clipboard = text; + } +} diff --git a/src/editor.rs b/src/editor.rs index 0ed2527..bdd85d2 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -13,7 +13,7 @@ use std::{ vec, }; -use crate::clipboard::Clipboard; +use crate::config::Config; use crate::util::{color_highlight, color_reset, read_line}; const TAB_SIZE: usize = 4; @@ -24,7 +24,6 @@ pub struct Editor { scroll: usize, cursor: Cursor, marker: Option, - clipboard: Clipboard, path: Option, active: bool, unsaved_changes: bool, @@ -41,7 +40,7 @@ struct Cursor { type Line = Range; impl Editor { - pub fn open_file(clipboard: Clipboard, path: PathBuf) -> std::io::Result { + pub fn open_file(path: PathBuf) -> std::io::Result { let text = fs::read_to_string(&path)?; Ok(Editor { text, @@ -49,7 +48,6 @@ impl Editor { scroll: 0, cursor: Cursor { line: 0, column: 0 }, marker: None, - clipboard, path: Some(path), active: false, unsaved_changes: false, @@ -57,14 +55,13 @@ impl Editor { }) } - pub fn new(clipboard: Clipboard, path: Option) -> Self { + pub fn new(path: Option) -> Self { Editor { text: String::new(), lines: vec![0..0], scroll: 0, cursor: Cursor { line: 0, column: 0 }, marker: None, - clipboard, path, active: false, unsaved_changes: true, @@ -90,18 +87,18 @@ impl Editor { self.path.as_ref() } - pub fn enter(&mut self) { + pub fn enter(&mut self, config: &mut Config) { self.active = true; self.find_lines(); while self.active { - self.draw(); + self.draw(config); self.message = None; - self.input(); + self.input(config); } } - fn input(&mut self) { + fn input(&mut self, config: &mut Config) { if let Ok(Event::Key(event)) = event::read() { if self.input_movement(&event) { return; @@ -122,9 +119,10 @@ impl Editor { }, KeyModifiers::CONTROL => match event.code { KeyCode::Char('s') => self.save(), - KeyCode::Char('c') => self.copy(), - KeyCode::Char('x') => self.cut(), - KeyCode::Char('v') => self.paste(), + KeyCode::Char('c') => self.copy(config), + KeyCode::Char('x') => self.cut(config), + KeyCode::Char('v') => self.paste(config), + KeyCode::Char('l') => config.line_numbers = !config.line_numbers, _ => (), }, _ => (), @@ -157,7 +155,7 @@ impl Editor { true } - fn draw(&self) { + fn draw(&self, config: &Config) { queue!(stdout(), Clear(ClearType::All)).unwrap(); let max_rows = terminal::size().unwrap().1 as usize - 1; @@ -166,11 +164,18 @@ impl Editor { let selection = self.selection().unwrap_or_default(); + let line_number_width = self.lines.len().to_string().len(); + for (line_index, line) in self.lines[visible_rows].iter().enumerate() { let text = &self.text[line.clone()]; queue!(stdout(), MoveTo(0, line_index as u16)).unwrap(); + if config.line_numbers { + let line_num = line_index + self.scroll + 1; + print!("{line_num:line_number_width$} "); + } + let mut in_selection = false; for (i, char) in text.char_indices() { let char_i = line.start + i; @@ -192,10 +197,15 @@ impl Editor { color_reset(); } self.status_line(); + let cursor_offset = if config.line_numbers { + line_number_width + 1 + } else { + 0 + }; queue!( stdout(), MoveTo( - self.physical_column() as u16, + (self.physical_column() + cursor_offset) as u16, (self.cursor.line - self.scroll) as u16 ), cursor::Show, @@ -212,15 +222,15 @@ impl Editor { print!("{message}"); } else { print!( - "({},{}) {}", - self.cursor.line, + "[{}, {}] {}", + self.cursor.line + 1, self.physical_column(), self.title(), ); } } - fn message(&mut self, text: String) { + fn set_message(&mut self, text: String) { self.message = Some(text); } @@ -351,16 +361,16 @@ impl Editor { self.selection().unwrap_or(self.current_line().clone()) } - fn copy(&mut self) { + fn copy(&mut self, config: &mut Config) { let range = self.selection_or_line(); let mut text = self.text[range].to_owned(); if self.marker.is_none() { text += "\n"; } - self.clipboard.set(text); + config.set_clipboard(text); } - fn cut(&mut self) { + fn cut(&mut self, config: &mut Config) { let range = self.selection_or_line(); let start = range.start; let mut end = range.end; @@ -370,19 +380,19 @@ impl Editor { end += 1; } end = end.min(self.text.len()); - self.clipboard.set(text); + config.set_clipboard(text); self.text = self.text[..start].to_owned() + &self.text[end..]; self.find_lines(); self.move_to_byte(start); self.marker = None; } - fn paste(&mut self) { + fn paste(&mut self, config: &Config) { self.unsaved_changes = true; let cursor = self.char_index(); - let new_text = self.clipboard.get(); + let new_text = config.clipboard(); let end_pos = cursor + new_text.len(); - self.text.insert_str(cursor, &new_text); + self.text.insert_str(cursor, new_text); self.find_lines(); self.move_to_byte(end_pos); self.marker = None; @@ -430,12 +440,12 @@ impl Editor { if let Some(path) = &self.path { match File::create(path) { Ok(mut file) => { - self.message(format!("Saved file as '{}'", path.display())); + self.set_message(format!("Saved file as '{}'", path.display())); file.write_all(self.text.as_bytes()).unwrap(); self.unsaved_changes = false; } Err(e) => { - self.message(format!("Could not save file as '{}': {e}", path.display())); + self.set_message(format!("Could not save file as '{}': {e}", path.display())); if filename_new { self.path = None; } diff --git a/src/main.rs b/src/main.rs index 58e0e0f..49cb6a9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,19 +15,19 @@ use std::{ process::exit, }; -mod clipboard; +mod config; mod editor; mod util; -use clipboard::Clipboard; +use config::Config; use editor::Editor; -use util::{color_highlight, color_reset, read_yes_no}; +use util::{ask_yes_no, color_highlight, color_reset}; fn main() { Navigator::new().run(); } struct Navigator { - clipboard: Clipboard, + config: Config, editors: Vec, files: Vec, selected: usize, @@ -40,11 +40,8 @@ struct Navigator { impl Navigator { fn new() -> Self { - let clipboard = Clipboard::new(); let mut editors = Vec::new(); - let args: Vec = env::args().skip(1).collect(); - let mut path = env::current_dir().unwrap(); for arg in args.iter().map(PathBuf::from) { @@ -52,19 +49,19 @@ impl Navigator { path = arg.canonicalize().unwrap(); break; } else if arg.is_file() { - if let Ok(editor) = Editor::open_file(clipboard.clone(), arg) { + if let Ok(editor) = Editor::open_file(arg) { editors.push(editor); } } else { - editors.push(Editor::new(clipboard.clone(), Some(arg))); + editors.push(Editor::new(Some(arg))); } } if args.is_empty() { - editors.push(Editor::new(clipboard.clone(), None)); + editors.push(Editor::new(None)); } let immediate_open = editors.len() == 1; Self { - clipboard, + config: Config::new(), editors, selected: 0, files: Vec::new(), @@ -214,7 +211,7 @@ impl Navigator { } // no editor exists with this path if selected == self.editors.len() { - match Editor::open_file(self.clipboard.clone(), path) { + match Editor::open_file(path) { Ok(editor) => self.editors.push(editor), Err(err) => { self.message(format!("Could not open file: {err}")); @@ -240,13 +237,13 @@ impl Navigator { fn open_selected(&mut self) { if self.selected < self.editors.len() { self.scroll = 0; - self.editors[self.selected].enter(); + self.editors[self.selected].enter(&mut self.config); } } fn new_editor(&mut self) { self.selected = self.editors.len(); - self.editors.push(Editor::new(self.clipboard.clone(), None)); + self.editors.push(Editor::new(None)); self.open_selected(); } @@ -272,10 +269,8 @@ impl Navigator { } fn quit(&self) { - if self.any_unsaved() { - if !read_yes_no("Unsaved changes, quit anyway?", false) { - return; - } + if self.any_unsaved() && !ask_yes_no("Unsaved changes, quit anyway?", false) { + return; } disable_raw_mode().unwrap(); execute!(stdout(), LeaveAlternateScreen, cursor::Show).unwrap(); diff --git a/src/util.rs b/src/util.rs index 46fd3e4..7838e2b 100644 --- a/src/util.rs +++ b/src/util.rs @@ -7,7 +7,7 @@ use crossterm::{ }; use std::io::{stdout, Write}; -pub fn read_yes_no(prompt: &str, default: bool) -> bool { +pub fn ask_yes_no(prompt: &str, default: bool) -> bool { let options = if default { "Y/n" } else { "y/N" }; let prompt = format!("{prompt} [{options}]: "); match read_line(&prompt).and_then(|s| s.chars().next()) {