refactor global state access, add line numbers
This commit is contained in:
parent
cbc924f46e
commit
29660e90c6
5 changed files with 72 additions and 88 deletions
|
@ -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
21
src/config.rs
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
29
src/main.rs
29
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<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,11 +269,9 @@ impl Navigator {
|
|||
}
|
||||
|
||||
fn quit(&self) {
|
||||
if self.any_unsaved() {
|
||||
if !read_yes_no("Unsaved changes, quit anyway?", false) {
|
||||
if self.any_unsaved() && !ask_yes_no("Unsaved changes, quit anyway?", false) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
disable_raw_mode().unwrap();
|
||||
execute!(stdout(), LeaveAlternateScreen, cursor::Show).unwrap();
|
||||
exit(0);
|
||||
|
|
|
@ -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()) {
|
||||
|
|
Loading…
Reference in a new issue