refactor global state access, add line numbers

This commit is contained in:
Crispy 2023-03-14 20:09:11 +01:00
parent cbc924f46e
commit 29660e90c6
5 changed files with 72 additions and 88 deletions

View file

@ -1,42 +0,0 @@
use std::{cell::RefCell, rc::Rc};
#[derive(Clone)]
pub struct Clipboard {
clipboard: Rc<RefCell<Internal>>,
}
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;
}
}

21
src/config.rs Normal file
View file

@ -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;
}
}

View file

@ -13,7 +13,7 @@ use std::{
vec, vec,
}; };
use crate::clipboard::Clipboard; use crate::config::Config;
use crate::util::{color_highlight, color_reset, read_line}; use crate::util::{color_highlight, color_reset, read_line};
const TAB_SIZE: usize = 4; const TAB_SIZE: usize = 4;
@ -24,7 +24,6 @@ pub struct Editor {
scroll: usize, scroll: usize,
cursor: Cursor, cursor: Cursor,
marker: Option<usize>, marker: Option<usize>,
clipboard: Clipboard,
path: Option<PathBuf>, path: Option<PathBuf>,
active: bool, active: bool,
unsaved_changes: bool, unsaved_changes: bool,
@ -41,7 +40,7 @@ struct Cursor {
type Line = Range<usize>; type Line = Range<usize>;
impl Editor { impl Editor {
pub fn open_file(clipboard: Clipboard, path: PathBuf) -> std::io::Result<Self> { pub fn open_file(path: PathBuf) -> std::io::Result<Self> {
let text = fs::read_to_string(&path)?; let text = fs::read_to_string(&path)?;
Ok(Editor { Ok(Editor {
text, text,
@ -49,7 +48,6 @@ impl Editor {
scroll: 0, scroll: 0,
cursor: Cursor { line: 0, column: 0 }, cursor: Cursor { line: 0, column: 0 },
marker: None, marker: None,
clipboard,
path: Some(path), path: Some(path),
active: false, active: false,
unsaved_changes: false, unsaved_changes: false,
@ -57,14 +55,13 @@ impl Editor {
}) })
} }
pub fn new(clipboard: Clipboard, path: Option<PathBuf>) -> Self { pub fn new(path: Option<PathBuf>) -> Self {
Editor { Editor {
text: String::new(), text: String::new(),
lines: vec![0..0], lines: vec![0..0],
scroll: 0, scroll: 0,
cursor: Cursor { line: 0, column: 0 }, cursor: Cursor { line: 0, column: 0 },
marker: None, marker: None,
clipboard,
path, path,
active: false, active: false,
unsaved_changes: true, unsaved_changes: true,
@ -90,18 +87,18 @@ impl Editor {
self.path.as_ref() self.path.as_ref()
} }
pub fn enter(&mut self) { pub fn enter(&mut self, config: &mut Config) {
self.active = true; self.active = true;
self.find_lines(); self.find_lines();
while self.active { while self.active {
self.draw(); self.draw(config);
self.message = None; 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 let Ok(Event::Key(event)) = event::read() {
if self.input_movement(&event) { if self.input_movement(&event) {
return; return;
@ -122,9 +119,10 @@ impl Editor {
}, },
KeyModifiers::CONTROL => match event.code { KeyModifiers::CONTROL => match event.code {
KeyCode::Char('s') => self.save(), KeyCode::Char('s') => self.save(),
KeyCode::Char('c') => self.copy(), KeyCode::Char('c') => self.copy(config),
KeyCode::Char('x') => self.cut(), KeyCode::Char('x') => self.cut(config),
KeyCode::Char('v') => self.paste(), KeyCode::Char('v') => self.paste(config),
KeyCode::Char('l') => config.line_numbers = !config.line_numbers,
_ => (), _ => (),
}, },
_ => (), _ => (),
@ -157,7 +155,7 @@ impl Editor {
true true
} }
fn draw(&self) { fn draw(&self, config: &Config) {
queue!(stdout(), Clear(ClearType::All)).unwrap(); queue!(stdout(), Clear(ClearType::All)).unwrap();
let max_rows = terminal::size().unwrap().1 as usize - 1; let max_rows = terminal::size().unwrap().1 as usize - 1;
@ -166,11 +164,18 @@ impl Editor {
let selection = self.selection().unwrap_or_default(); 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() { for (line_index, line) in self.lines[visible_rows].iter().enumerate() {
let text = &self.text[line.clone()]; let text = &self.text[line.clone()];
queue!(stdout(), MoveTo(0, line_index as u16)).unwrap(); 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; let mut in_selection = false;
for (i, char) in text.char_indices() { for (i, char) in text.char_indices() {
let char_i = line.start + i; let char_i = line.start + i;
@ -192,10 +197,15 @@ impl Editor {
color_reset(); color_reset();
} }
self.status_line(); self.status_line();
let cursor_offset = if config.line_numbers {
line_number_width + 1
} else {
0
};
queue!( queue!(
stdout(), stdout(),
MoveTo( MoveTo(
self.physical_column() as u16, (self.physical_column() + cursor_offset) as u16,
(self.cursor.line - self.scroll) as u16 (self.cursor.line - self.scroll) as u16
), ),
cursor::Show, cursor::Show,
@ -212,15 +222,15 @@ impl Editor {
print!("{message}"); print!("{message}");
} else { } else {
print!( print!(
"({},{}) {}", "[{}, {}] {}",
self.cursor.line, self.cursor.line + 1,
self.physical_column(), self.physical_column(),
self.title(), self.title(),
); );
} }
} }
fn message(&mut self, text: String) { fn set_message(&mut self, text: String) {
self.message = Some(text); self.message = Some(text);
} }
@ -351,16 +361,16 @@ impl Editor {
self.selection().unwrap_or(self.current_line().clone()) 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 range = self.selection_or_line();
let mut text = self.text[range].to_owned(); let mut text = self.text[range].to_owned();
if self.marker.is_none() { if self.marker.is_none() {
text += "\n"; 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 range = self.selection_or_line();
let start = range.start; let start = range.start;
let mut end = range.end; let mut end = range.end;
@ -370,19 +380,19 @@ impl Editor {
end += 1; end += 1;
} }
end = end.min(self.text.len()); 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.text = self.text[..start].to_owned() + &self.text[end..];
self.find_lines(); self.find_lines();
self.move_to_byte(start); self.move_to_byte(start);
self.marker = None; self.marker = None;
} }
fn paste(&mut self) { fn paste(&mut self, config: &Config) {
self.unsaved_changes = true; self.unsaved_changes = true;
let cursor = self.char_index(); let cursor = self.char_index();
let new_text = self.clipboard.get(); let new_text = config.clipboard();
let end_pos = cursor + new_text.len(); 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.find_lines();
self.move_to_byte(end_pos); self.move_to_byte(end_pos);
self.marker = None; self.marker = None;
@ -430,12 +440,12 @@ impl Editor {
if let Some(path) = &self.path { if let Some(path) = &self.path {
match File::create(path) { match File::create(path) {
Ok(mut file) => { 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(); file.write_all(self.text.as_bytes()).unwrap();
self.unsaved_changes = false; self.unsaved_changes = false;
} }
Err(e) => { 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 { if filename_new {
self.path = None; self.path = None;
} }

View file

@ -15,19 +15,19 @@ use std::{
process::exit, process::exit,
}; };
mod clipboard; mod config;
mod editor; mod editor;
mod util; mod util;
use clipboard::Clipboard; use config::Config;
use editor::Editor; use editor::Editor;
use util::{color_highlight, color_reset, read_yes_no}; use util::{ask_yes_no, color_highlight, color_reset};
fn main() { fn main() {
Navigator::new().run(); Navigator::new().run();
} }
struct Navigator { struct Navigator {
clipboard: Clipboard, config: Config,
editors: Vec<Editor>, editors: Vec<Editor>,
files: Vec<PathBuf>, files: Vec<PathBuf>,
selected: usize, selected: usize,
@ -40,11 +40,8 @@ struct Navigator {
impl Navigator { impl Navigator {
fn new() -> Self { fn new() -> Self {
let clipboard = Clipboard::new();
let mut editors = Vec::new(); let mut editors = Vec::new();
let args: Vec<String> = env::args().skip(1).collect(); let args: Vec<String> = env::args().skip(1).collect();
let mut path = env::current_dir().unwrap(); let mut path = env::current_dir().unwrap();
for arg in args.iter().map(PathBuf::from) { for arg in args.iter().map(PathBuf::from) {
@ -52,19 +49,19 @@ impl Navigator {
path = arg.canonicalize().unwrap(); path = arg.canonicalize().unwrap();
break; break;
} else if arg.is_file() { } 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); editors.push(editor);
} }
} else { } else {
editors.push(Editor::new(clipboard.clone(), Some(arg))); editors.push(Editor::new(Some(arg)));
} }
} }
if args.is_empty() { if args.is_empty() {
editors.push(Editor::new(clipboard.clone(), None)); editors.push(Editor::new(None));
} }
let immediate_open = editors.len() == 1; let immediate_open = editors.len() == 1;
Self { Self {
clipboard, config: Config::new(),
editors, editors,
selected: 0, selected: 0,
files: Vec::new(), files: Vec::new(),
@ -214,7 +211,7 @@ impl Navigator {
} }
// no editor exists with this path // no editor exists with this path
if selected == self.editors.len() { if selected == self.editors.len() {
match Editor::open_file(self.clipboard.clone(), path) { match Editor::open_file(path) {
Ok(editor) => self.editors.push(editor), Ok(editor) => self.editors.push(editor),
Err(err) => { Err(err) => {
self.message(format!("Could not open file: {err}")); self.message(format!("Could not open file: {err}"));
@ -240,13 +237,13 @@ impl Navigator {
fn open_selected(&mut self) { fn open_selected(&mut self) {
if self.selected < self.editors.len() { if self.selected < self.editors.len() {
self.scroll = 0; self.scroll = 0;
self.editors[self.selected].enter(); self.editors[self.selected].enter(&mut self.config);
} }
} }
fn new_editor(&mut self) { fn new_editor(&mut self) {
self.selected = self.editors.len(); self.selected = self.editors.len();
self.editors.push(Editor::new(self.clipboard.clone(), None)); self.editors.push(Editor::new(None));
self.open_selected(); self.open_selected();
} }
@ -272,10 +269,8 @@ impl Navigator {
} }
fn quit(&self) { fn quit(&self) {
if self.any_unsaved() { if self.any_unsaved() && !ask_yes_no("Unsaved changes, quit anyway?", false) {
if !read_yes_no("Unsaved changes, quit anyway?", false) { return;
return;
}
} }
disable_raw_mode().unwrap(); disable_raw_mode().unwrap();
execute!(stdout(), LeaveAlternateScreen, cursor::Show).unwrap(); execute!(stdout(), LeaveAlternateScreen, cursor::Show).unwrap();

View file

@ -7,7 +7,7 @@ use crossterm::{
}; };
use std::io::{stdout, Write}; 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 options = if default { "Y/n" } else { "y/N" };
let prompt = format!("{prompt} [{options}]: "); let prompt = format!("{prompt} [{options}]: ");
match read_line(&prompt).and_then(|s| s.chars().next()) { match read_line(&prompt).and_then(|s| s.chars().next()) {