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,
};
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<usize>,
clipboard: Clipboard,
path: Option<PathBuf>,
active: bool,
unsaved_changes: bool,
@ -41,7 +40,7 @@ struct Cursor {
type Line = Range<usize>;
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)?;
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<PathBuf>) -> Self {
pub fn new(path: Option<PathBuf>) -> 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;
}

View file

@ -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<Editor>,
files: Vec<PathBuf>,
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<String> = 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();

View file

@ -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()) {