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,
|
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;
|
||||||
}
|
}
|
||||||
|
|
29
src/main.rs
29
src/main.rs
|
@ -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,11 +269,9 @@ 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();
|
||||||
exit(0);
|
exit(0);
|
||||||
|
|
|
@ -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()) {
|
||||||
|
|
Loading…
Reference in a new issue