feat: Support creating new entries

Signed-off-by: Awiteb <a@4rs.nl>
This commit is contained in:
Awiteb 2024-12-27 21:47:15 +00:00
parent 6acffbf9e1
commit 626b4f8cc5
Signed by: awiteb
GPG key ID: 3F6B55640AA6682F
12 changed files with 113 additions and 14 deletions

View file

@ -22,8 +22,8 @@ cargo install --path . # this will install the binary in $HOME/.cargo/bin
`../new_name.rs` will move the file to the parent directory and rename it to `../new_name.rs` will move the file to the parent directory and rename it to
`new_name.rs`. `new_name.rs`.
- You can delete a file/directory with `ctrl+d` keybinding - You can delete a file/directory with `ctrl+d` keybinding
<!-- - You can create a new file with `n+f` keybinding --> - You can create a new file with `n+f` keybinding
<!-- - You can create a new directory with `n+d` keybinding --> - You can create a new directory with `n+d` keybinding
## Implemented features ## Implemented features
@ -58,6 +58,7 @@ The key bindings are configurable. For the set of configurable keys and key comb
| `left arrow` | `--keybinding.collapse_dir=left` | collapse an entry directory or jump to parent if not collapsable | | `left arrow` | `--keybinding.collapse_dir=left` | collapse an entry directory or jump to parent if not collapsable |
| `right arrow` | `--keybinding.expand_dir=left` | expand an entry directory | | `right arrow` | `--keybinding.expand_dir=left` | expand an entry directory |
| `r` | `--keybinding.rename_mode=r` | Enter rename mode (To exit rename mode press `quit` keybinding) | | `r` | `--keybinding.rename_mode=r` | Enter rename mode (To exit rename mode press `quit` keybinding) |
| `n` | `--keybinding.new_entry=n` | Enter create mode (To exit rename mode press `quit` keybinding) |
| `ctrl+r` | `--keybinding.reload=r` | collapse all directories and reload root directory | | `ctrl+r` | `--keybinding.reload=r` | collapse all directories and reload root directory |
| `ctrl+d` | `--keybinding.remove=ctrl+d` | remove a file/directory | | `ctrl+d` | `--keybinding.remove=ctrl+d` | remove a file/directory |
| `return` | `--keybinding.file_action=return` | perform configured file action | | `return` | `--keybinding.file_action=return` | perform configured file action |

View file

@ -15,6 +15,7 @@ mod entry_up;
mod expand_dir; mod expand_dir;
mod file_action; mod file_action;
mod modes; mod modes;
mod new_entry;
mod quit; mod quit;
mod reload; mod reload;
mod remove; mod remove;
@ -33,7 +34,17 @@ impl<W: Write> EventQueue<W> {
(Mode::Normal, k) if k == Key::from(&ck.quit) => self.do_quit(), (Mode::Normal, k) if k == Key::from(&ck.quit) => self.do_quit(),
(Mode::Normal, k) if k == Key::from(&ck.reload) => self.do_reload(), (Mode::Normal, k) if k == Key::from(&ck.reload) => self.do_reload(),
(Mode::Normal, k) if k == Key::from(&ck.rename_mode) => self.do_enter_rename_mode(), (Mode::Normal, k) if k == Key::from(&ck.rename_mode) => self.do_enter_rename_mode(),
(Mode::Normal, k) if k == Key::from(&ck.new_entry) => self.do_enter_create_mode(),
(Mode::Normal, k) if k == Key::from(&ck.remove) => self.do_remove_entry(), (Mode::Normal, k) if k == Key::from(&ck.remove) => self.do_remove_entry(),
(Mode::CreateEntry, k) if k == Key::from(&ck.quit) => self.do_enter_normal_mode(),
(Mode::CreateEntry, k) if k == Key::from("f") => {
self.do_create_file();
self.do_enter_normal_mode()
}
(Mode::CreateEntry, k) if k == Key::from("d") => {
self.do_create_dir();
self.do_enter_normal_mode()
}
(Mode::Rename(..), k) if k == Key::from(&ck.quit) => self.do_enter_normal_mode(), (Mode::Rename(..), k) if k == Key::from(&ck.quit) => self.do_enter_normal_mode(),
(Mode::Rename(new_name), k) if k == Key::from("return") => { (Mode::Rename(new_name), k) if k == Key::from("return") => {
if !new_name.chars().all(char::is_whitespace) { if !new_name.chars().all(char::is_whitespace) {
@ -47,7 +58,7 @@ impl<W: Write> EventQueue<W> {
if let Cow::Owned(updated_name) = self.do_handle_rename_input(&new_name, k.inner()) if let Cow::Owned(updated_name) = self.do_handle_rename_input(&new_name, k.inner())
{ {
self.pager.mode = Mode::Rename(updated_name); self.pager.mode = Mode::Rename(updated_name);
self.update_pager(0); self.do_reload();
} }
Some(()) Some(())
} }

View file

@ -8,19 +8,25 @@ use crate::{controller::EventQueue, view::Mode};
impl<W: Write> EventQueue<W> { impl<W: Write> EventQueue<W> {
pub fn do_enter_normal_mode(&mut self) -> Option<()> { pub fn do_enter_normal_mode(&mut self) -> Option<()> {
self.pager.mode = Mode::Normal; self.pager.mode = Mode::Normal;
self.update_pager(0); self.do_reload();
Some(()) Some(())
} }
pub fn do_enter_rename_mode(&mut self) -> Option<()> { pub fn do_enter_rename_mode(&mut self) -> Option<()> {
self.pager.mode = Mode::Rename( self.pager.mode = Mode::Rename(
self.pager self.pager
.current_entry .current_entry
.path
.file_name() .file_name()
.unwrap() .unwrap()
.to_string_lossy() .to_string_lossy()
.to_string(), .to_string(),
); );
self.update_pager(0); self.do_reload();
Some(())
}
pub fn do_enter_create_mode(&mut self) -> Option<()> {
self.pager.mode = Mode::CreateEntry;
self.do_reload();
Some(()) Some(())
} }
} }

View file

@ -0,0 +1,44 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2024 Awiteb <a@4rs.nl>
use std::{fs, io::Write};
use crate::controller::EventQueue;
/// Default name for new files, if the name already exists, a number will be appended
const NEW_FILE_NAME: &str = "new_file";
/// Default name for new directories, if the name already exists, a number will be appended
const NEW_DIR_NAME: &str = "new_dir";
impl<W: Write> EventQueue<W> {
/// Create a new file in the current directory
pub fn do_create_file(&mut self) -> Option<()> {
let mut counter = 1;
let mut new_file_path = if self.pager.current_entry.is_expanded {
self.pager.current_entry.path.join(NEW_FILE_NAME)
} else {
self.pager.current_entry.path.with_file_name(NEW_FILE_NAME)
};
while new_file_path.exists() {
new_file_path = new_file_path.with_file_name(format!("{NEW_FILE_NAME}{counter}"));
counter += 1
}
let _ = fs::File::create(new_file_path);
Some(())
}
/// Create a new directory in the current directory
pub fn do_create_dir(&mut self) -> Option<()> {
let mut counter = 1;
let mut new_dir_path = if self.pager.current_entry.is_expanded {
self.pager.current_entry.path.join(NEW_DIR_NAME)
} else {
self.pager.current_entry.path.with_file_name(NEW_DIR_NAME)
};
while new_dir_path.exists() {
new_dir_path = new_dir_path.with_file_name(format!("{NEW_DIR_NAME}{counter}"));
counter += 1
}
let _ = fs::create_dir(new_dir_path);
Some(())
}
}

View file

@ -8,10 +8,10 @@ use crate::controller::EventQueue;
impl<W: Write> EventQueue<W> { impl<W: Write> EventQueue<W> {
/// Remove the current entry /// Remove the current entry
pub fn do_remove_entry(&mut self) -> Option<()> { pub fn do_remove_entry(&mut self) -> Option<()> {
if self.pager.current_entry.is_dir() { if self.pager.current_entry.path.is_dir() {
let _ = fs::remove_dir_all(&self.pager.current_entry); let _ = fs::remove_dir_all(&self.pager.current_entry.path);
} else { } else {
let _ = fs::remove_file(&self.pager.current_entry); let _ = fs::remove_file(&self.pager.current_entry.path);
} }
self.do_reload(); self.do_reload();
Some(()) Some(())

View file

@ -28,9 +28,9 @@ impl<W: Write> EventQueue<W> {
/// Rename the current file to the new name. /// Rename the current file to the new name.
pub fn do_rename_current_file(&mut self, new_name: &str) -> Option<()> { pub fn do_rename_current_file(&mut self, new_name: &str) -> Option<()> {
let new_path = self.pager.current_entry.with_file_name(new_name); let new_path = self.pager.current_entry.path.with_file_name(new_name);
if !new_path.exists() { if !new_path.exists() {
let _ = fs::rename(&self.pager.current_entry, new_path); let _ = fs::rename(&self.pager.current_entry.path, new_path);
}; };
Some(()) Some(())
} }

View file

@ -24,6 +24,8 @@ pub struct Keybinding {
pub rename_mode: String, pub rename_mode: String,
#[serde(default = "Keybinding::default_remove")] #[serde(default = "Keybinding::default_remove")]
pub remove: String, pub remove: String,
#[serde(default = "Keybinding::default_new_entry")]
pub new_entry: String,
} }
impl Default for Keybinding { impl Default for Keybinding {
@ -38,6 +40,7 @@ impl Default for Keybinding {
reload: Self::default_reload(), reload: Self::default_reload(),
rename_mode: Self::default_rename_mode(), rename_mode: Self::default_rename_mode(),
remove: Self::default_remove(), remove: Self::default_remove(),
new_entry: Self::default_new_entry(),
} }
} }
} }
@ -70,4 +73,7 @@ impl Keybinding {
fn default_remove() -> String { fn default_remove() -> String {
String::from("ctrl+d") String::from("ctrl+d")
} }
fn default_new_entry() -> String {
String::from("n")
}
} }

View file

@ -80,6 +80,9 @@ impl Config {
"--keybinding.entry_up" => config.keybinding.entry_up = Self::parse_value((key, value)), "--keybinding.entry_up" => config.keybinding.entry_up = Self::parse_value((key, value)),
"--keybinding.expand_dir" => config.keybinding.expand_dir = Self::parse_value((key, value)), "--keybinding.expand_dir" => config.keybinding.expand_dir = Self::parse_value((key, value)),
"--keybinding.file_action" => config.keybinding.file_action = Self::parse_value((key, value)), "--keybinding.file_action" => config.keybinding.file_action = Self::parse_value((key, value)),
"--keybinding.rename_mode" => config.keybinding.rename_mode = Self::parse_value((key, value)),
"--keybinding.new_entry" => config.keybinding.new_entry = Self::parse_value((key, value)),
"--keybinding.remove" => config.keybinding.remove = Self::parse_value((key, value)),
"--keybinding.quit" => config.keybinding.quit = Self::parse_value((key, value)), "--keybinding.quit" => config.keybinding.quit = Self::parse_value((key, value)),
"--keybinding.reload" => config.keybinding.reload = Self::parse_value((key, value)), "--keybinding.reload" => config.keybinding.reload = Self::parse_value((key, value)),
"--setup.working_dir" => config.setup.working_dir = Self::parse_value((key, value)), "--setup.working_dir" => config.setup.working_dir = Self::parse_value((key, value)),

View file

@ -15,17 +15,36 @@ mod print;
mod scroll; mod scroll;
mod update; mod update;
pub struct CurrentEntry {
pub path: PathBuf,
pub is_expanded: bool,
}
pub struct Pager<W: Write> { pub struct Pager<W: Write> {
config: Config, config: Config,
pub mode: Mode, pub mode: Mode,
pub cursor_row: i32, pub cursor_row: i32,
pub current_entry: PathBuf, pub current_entry: CurrentEntry,
out: W, out: W,
terminal_cols: i32, terminal_cols: i32,
terminal_rows: i32, terminal_rows: i32,
text_row: i32, text_row: i32,
} }
impl CurrentEntry {
fn new(path: impl AsRef<Path>) -> Self {
Self {
path: path.as_ref().to_path_buf(),
is_expanded: false,
}
}
fn expand(mut self, yes: bool) -> Self {
self.is_expanded = yes;
self
}
}
impl<W: Write> Pager<W> { impl<W: Write> Pager<W> {
pub fn new(config: Config, mut out: W) -> Self { pub fn new(config: Config, mut out: W) -> Self {
info!("initializing pager"); info!("initializing pager");
@ -40,7 +59,7 @@ impl<W: Write> Pager<W> {
.unwrap(); .unwrap();
Self { Self {
current_entry: Path::new(&config.setup.working_dir).to_path_buf(), current_entry: CurrentEntry::new(&config.setup.working_dir),
config, config,
cursor_row: 0, cursor_row: 0,
out, out,

View file

@ -8,6 +8,8 @@ pub enum Mode {
Normal, Normal,
/// Rename mode. Renaming a file. /// Rename mode. Renaming a file.
Rename(String), Rename(String),
/// Create entry mode. Creating a new file and directory.
CreateEntry,
} }
impl Mode { impl Mode {

View file

@ -6,6 +6,8 @@ use crate::{controller::Entrie, view::Pager};
use std::{io::Write, path}; use std::{io::Write, path};
use termion::terminal_size; use termion::terminal_size;
use super::CurrentEntry;
impl<W: Write> Pager<W> { impl<W: Write> Pager<W> {
fn update_terminal_size(&mut self) { fn update_terminal_size(&mut self) {
let (terminal_cols_raw, terminal_rows_raw) = terminal_size().unwrap(); let (terminal_cols_raw, terminal_rows_raw) = terminal_size().unwrap();
@ -59,8 +61,12 @@ impl<W: Write> Pager<W> {
let entry = &entries[index as usize]; let entry = &entries[index as usize];
if index == self.cursor_row { if index == self.cursor_row {
self.current_entry = self.current_entry = CurrentEntry::new(
path::absolute(&entry.path).unwrap_or_else(|_| entry.path.clone()); path::absolute(&entry.path).unwrap_or_else(|_| entry.path.clone()),
)
.expand(
entry.path.is_dir() && entry.display_text.trim().starts_with(['▼', 'v']),
);
let rename_name = self.mode.new_name().map(|new_name| { let rename_name = self.mode.new_name().map(|new_name| {
let filename = entry.path.file_name().unwrap().to_str().unwrap(); let filename = entry.path.file_name().unwrap().to_str().unwrap();

View file

@ -52,6 +52,7 @@ reload = "ctrl+r"
skip_up = "ctrl+up" skip_up = "ctrl+up"
skip_down = "ctrl+down" skip_down = "ctrl+down"
remove = "d" remove = "d"
new_entry = "n"
[setup] [setup]
# the working directory used when starting # the working directory used when starting