From 15beff91b3cfcbc1c688dc9dde7183ba8aa2b081 Mon Sep 17 00:00:00 2001 From: CrispyPin Date: Sun, 5 Mar 2023 18:06:29 +0100 Subject: [PATCH] file navigation menu --- src/editor.rs | 94 ++++++++++++++++++++++----------------- src/main.rs | 119 +++++++++++++++++++++++++++++++++++++++++--------- src/util.rs | 7 ++- 3 files changed, 158 insertions(+), 62 deletions(-) diff --git a/src/editor.rs b/src/editor.rs index e5ccfef..64e48ba 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -9,6 +9,7 @@ use std::{ fs::{self, File}, io::{stdout, Write}, ops::Range, + path::PathBuf, vec, }; @@ -24,7 +25,7 @@ pub struct Editor { cursor: Cursor, marker: Option, clipboard: Clipboard, - path: Option, + path: Option, active: bool, unsaved_changes: bool, } @@ -39,9 +40,9 @@ struct Cursor { type Line = Range; impl Editor { - pub fn new(clipboard: Clipboard, path: String) -> Self { - let text = fs::read_to_string(&path).unwrap_or_default(); - let mut this = Editor { + pub fn open_file(clipboard: Clipboard, path: PathBuf) -> Option { + let text = fs::read_to_string(&path).ok()?; + Some(Editor { text, lines: Vec::new(), scroll: 0, @@ -51,9 +52,7 @@ impl Editor { path: Some(path), active: false, unsaved_changes: false, - }; - this.find_lines(); - this + }) } pub fn new_empty(clipboard: Clipboard) -> Self { @@ -70,16 +69,36 @@ impl Editor { } } - pub fn name(&self) -> &str { - self.path.as_ref().map_or("untitled", |s| s) + pub fn new_named(clipboard: Clipboard, path: PathBuf) -> Self { + Editor { + text: String::new(), + lines: vec![0..0], + scroll: 0, + cursor: Cursor { line: 0, column: 0 }, + marker: None, + clipboard, + path: Some(path), + active: false, + unsaved_changes: true, + } + } + + pub fn name(&self) -> String { + if let Some(path) = &self.path { + if let Some(name) = path.file_name() { + return name.to_string_lossy().to_string(); + } + } + "untitled".into() } pub fn has_unsaved_changes(&self) -> bool { self.unsaved_changes } - pub fn open(&mut self) { + pub fn enter(&mut self) { self.active = true; + self.find_lines(); while self.active { self.draw(); @@ -88,31 +107,28 @@ impl Editor { } fn input(&mut self) { - match event::read() { - Ok(Event::Key(event)) => { - if self.input_movement(&event) { - return; - } - match event.modifiers { - KeyModifiers::NONE => match event.code { - KeyCode::Esc => self.active = false, - KeyCode::Char(ch) => self.insert_char(ch), - KeyCode::Enter => self.insert_char('\n'), - KeyCode::Backspace => self.backspace(), - KeyCode::Delete => self.delete(), - _ => (), - }, - 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(), - _ => (), - }, - _ => (), - } + if let Ok(Event::Key(event)) = event::read() { + if self.input_movement(&event) { + return; + } + match event.modifiers { + KeyModifiers::NONE => match event.code { + KeyCode::Esc => self.active = false, + KeyCode::Char(ch) => self.insert_char(ch), + KeyCode::Enter => self.insert_char('\n'), + KeyCode::Backspace => self.backspace(), + KeyCode::Delete => self.delete(), + _ => (), + }, + 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(), + _ => (), + }, + _ => (), } - _ => (), } } @@ -330,11 +346,8 @@ impl Editor { fn selection(&self) -> Option> { let cursor = self.char_index(); - if let Some(marker) = self.marker { - Some(marker.min(cursor)..(marker.max(cursor))) - } else { - None - } + self.marker + .map(|marker| marker.min(cursor)..(marker.max(cursor))) } fn selection_or_line(&self) -> Range { @@ -359,6 +372,7 @@ impl Editor { text += "\n"; end += 1; } + end = end.min(self.text.len()); self.clipboard.set(text); self.text = self.text[..start].to_owned() + &self.text[end..]; self.find_lines(); @@ -411,7 +425,7 @@ impl Editor { fn save(&mut self) { if self.path.is_none() { - self.path = read_line("Enter path: "); + self.path = read_line("Enter path: ").map(PathBuf::from); if self.path.is_none() { return; } diff --git a/src/main.rs b/src/main.rs index 92c030e..56287a7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,8 +9,9 @@ use crossterm::{ }, }; use std::{ - env, + env, fs, io::{stdout, Write}, + path::PathBuf, process::exit, }; @@ -25,25 +26,46 @@ fn main() { } struct Navigator { - editors: Vec, - selected: Option, clipboard: Clipboard, + editors: Vec, + files: Vec, + selected: usize, + path: PathBuf, + immediate_open: bool, } impl Navigator { fn new() -> Self { let clipboard = Clipboard::new(); - let mut editors: Vec = env::args() - .skip(1) - .map(|path| Editor::new(clipboard.clone(), path)) - .collect(); - if editors.is_empty() { + 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) { + if arg.is_dir() { + path = arg.canonicalize().unwrap(); + break; + } else if arg.is_file() { + if let Some(editor) = Editor::open_file(clipboard.clone(), arg) { + editors.push(editor); + } + } else { + editors.push(Editor::new_named(clipboard.clone(), arg)) + } + } + if args.is_empty() { editors.push(Editor::new_empty(clipboard.clone())); } + let immediate_open = editors.len() == 1; Self { - editors, - selected: Some(0), clipboard, + editors, + selected: 0, + files: Vec::new(), + path, + immediate_open, } } @@ -51,7 +73,12 @@ impl Navigator { execute!(stdout(), EnterAlternateScreen, Clear(ClearType::All)).unwrap(); enable_raw_mode().unwrap(); + if self.immediate_open { + self.enter(); + } + loop { + self.get_files(); self.draw(); self.input(); } @@ -62,7 +89,7 @@ impl Navigator { print!("Open editors: {}", self.editors.len()); for (index, editor) in self.editors.iter().enumerate() { - if Some(index) == self.selected { + if index == self.selected { queue!(stdout(), SetColors(Colors::new(Color::Black, Color::White))).unwrap(); } queue!(stdout(), MoveTo(1, index as u16 + 1)).unwrap(); @@ -74,6 +101,26 @@ impl Navigator { queue!(stdout(), ResetColor).unwrap(); } + let offset = self.editors.len() as u16 + 2; + queue!(stdout(), MoveTo(0, offset)).unwrap(); + + print!("Current dir: {}", self.path.to_string_lossy()); + for (index, path) in self.files.iter().enumerate() { + if index == self.selected.wrapping_sub(self.editors.len()) { + queue!(stdout(), SetColors(Colors::new(Color::Black, Color::White))).unwrap(); + } + queue!(stdout(), MoveTo(1, index as u16 + 1 + offset)).unwrap(); + if let Some(name) = path.file_name() { + print!("{}", name.to_string_lossy()); + } else { + print!("{}", path.to_string_lossy()); + } + if path.is_dir() { + print!("/"); + } + queue!(stdout(), ResetColor).unwrap(); + } + stdout().flush().unwrap(); } @@ -83,7 +130,8 @@ impl Navigator { KeyCode::Char('q') => self.quit(), KeyCode::Up => self.nav_up(), KeyCode::Down => self.nav_down(), - KeyCode::Enter => self.open_selected(), + KeyCode::Enter => self.enter(), + KeyCode::Home => self.path = env::current_dir().unwrap(), KeyCode::Char('n') => { if event.modifiers == KeyModifiers::CONTROL { self.new_editor(); @@ -95,31 +143,60 @@ impl Navigator { } fn nav_up(&mut self) { - if self.selected > Some(0) { - self.selected = Some(self.selected.unwrap() - 1); - } + self.selected = self.selected.saturating_sub(1); } fn nav_down(&mut self) { - if let Some(index) = self.selected.as_mut() { - if *index < self.editors.len() - 1 { - *index += 1; + self.selected = (self.selected + 1).min(self.editors.len() + self.files.len() - 1); + } + + fn enter(&mut self) { + if self.selected < self.editors.len() { + self.editors[self.selected].enter(); + } else { + let i = self.selected - self.editors.len(); + if i == 0 { + if let Some(parent) = self.path.parent() { + self.path = parent.to_owned() + } + } else { + let path = &self.files[i]; + if path.is_dir() { + self.path = self.path.join(path); + self.selected = self.editors.len(); + } else if path.is_file() { + if let Some(editor) = + Editor::open_file(self.clipboard.clone(), path.canonicalize().unwrap()) + { + self.selected = self.editors.len(); + self.editors.push(editor); + self.open_selected() + } + } } } } fn open_selected(&mut self) { - if let Some(index) = self.selected { - self.editors[index].open(); + if self.selected < self.editors.len() { + self.editors[self.selected].enter(); } } fn new_editor(&mut self) { - self.selected = Some(self.editors.len()); + self.selected = self.editors.len(); self.editors.push(Editor::new_empty(self.clipboard.clone())); self.open_selected(); } + fn get_files(&mut self) { + self.files.clear(); + self.files.push(PathBuf::from("..")); + for file in fs::read_dir(&self.path).unwrap().flatten() { + self.files.push(file.path()); + } + } + fn quit(&self) { disable_raw_mode().unwrap(); execute!(stdout(), LeaveAlternateScreen, cursor::Show).unwrap(); diff --git a/src/util.rs b/src/util.rs index 24f243a..ce3c82a 100644 --- a/src/util.rs +++ b/src/util.rs @@ -9,7 +9,10 @@ pub fn read_line(prompt: &str) -> Option { let mut response = String::new(); let size = terminal::size().unwrap(); let start_pos = cursor::MoveTo(0, size.1); + let width = size.0 as usize; + queue!(stdout(), start_pos).unwrap(); + print!("{:width$}", " "); queue!(stdout(), start_pos).unwrap(); print!("{prompt}"); stdout().flush().unwrap(); @@ -20,6 +23,8 @@ pub fn read_line(prompt: &str) -> Option { KeyCode::Enter => break, KeyCode::Char(ch) => response.push(ch), KeyCode::Backspace => { + queue!(stdout(), start_pos).unwrap(); + print!("{:width$}", " "); response.pop(); } KeyCode::Esc => return None, @@ -27,7 +32,7 @@ pub fn read_line(prompt: &str) -> Option { } } queue!(stdout(), start_pos).unwrap(); - print!("{prompt}{response} "); + print!("{prompt}{response}"); stdout().flush().unwrap(); } Some(response.trim().into())