diff --git a/README.md b/README.md index e9177e2..09140b9 100644 --- a/README.md +++ b/README.md @@ -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`. - 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 directory with `n+d` keybinding ## 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 | | `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) | +| `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+d` | `--keybinding.remove=ctrl+d` | remove a file/directory | | `return` | `--keybinding.file_action=return` | perform configured file action | diff --git a/src/controller/key_event_matcher/mod.rs b/src/controller/key_event_matcher/mod.rs index 29d7421..7f23571 100644 --- a/src/controller/key_event_matcher/mod.rs +++ b/src/controller/key_event_matcher/mod.rs @@ -15,6 +15,7 @@ mod entry_up; mod expand_dir; mod file_action; mod modes; +mod new_entry; mod quit; mod reload; mod remove; @@ -33,7 +34,17 @@ impl EventQueue { (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.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::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(new_name), k) if k == Key::from("return") => { if !new_name.chars().all(char::is_whitespace) { @@ -47,7 +58,7 @@ impl EventQueue { if let Cow::Owned(updated_name) = self.do_handle_rename_input(&new_name, k.inner()) { self.pager.mode = Mode::Rename(updated_name); - self.update_pager(0); + self.do_reload(); } Some(()) } diff --git a/src/controller/key_event_matcher/modes.rs b/src/controller/key_event_matcher/modes.rs index 975e0ef..0991698 100644 --- a/src/controller/key_event_matcher/modes.rs +++ b/src/controller/key_event_matcher/modes.rs @@ -8,19 +8,25 @@ use crate::{controller::EventQueue, view::Mode}; impl EventQueue { pub fn do_enter_normal_mode(&mut self) -> Option<()> { self.pager.mode = Mode::Normal; - self.update_pager(0); + self.do_reload(); Some(()) } pub fn do_enter_rename_mode(&mut self) -> Option<()> { self.pager.mode = Mode::Rename( self.pager .current_entry + .path .file_name() .unwrap() .to_string_lossy() .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(()) } } diff --git a/src/controller/key_event_matcher/new_entry.rs b/src/controller/key_event_matcher/new_entry.rs new file mode 100644 index 0000000..17e5379 --- /dev/null +++ b/src/controller/key_event_matcher/new_entry.rs @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2024 Awiteb + +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 EventQueue { + /// 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(()) + } +} diff --git a/src/controller/key_event_matcher/remove.rs b/src/controller/key_event_matcher/remove.rs index 9f455b6..6c47518 100644 --- a/src/controller/key_event_matcher/remove.rs +++ b/src/controller/key_event_matcher/remove.rs @@ -8,10 +8,10 @@ use crate::controller::EventQueue; impl EventQueue { /// Remove the current entry pub fn do_remove_entry(&mut self) -> Option<()> { - if self.pager.current_entry.is_dir() { - let _ = fs::remove_dir_all(&self.pager.current_entry); + if self.pager.current_entry.path.is_dir() { + let _ = fs::remove_dir_all(&self.pager.current_entry.path); } else { - let _ = fs::remove_file(&self.pager.current_entry); + let _ = fs::remove_file(&self.pager.current_entry.path); } self.do_reload(); Some(()) diff --git a/src/controller/key_event_matcher/rename.rs b/src/controller/key_event_matcher/rename.rs index 425465d..0a055e8 100644 --- a/src/controller/key_event_matcher/rename.rs +++ b/src/controller/key_event_matcher/rename.rs @@ -28,9 +28,9 @@ impl EventQueue { /// Rename the current file to the new name. 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() { - let _ = fs::rename(&self.pager.current_entry, new_path); + let _ = fs::rename(&self.pager.current_entry.path, new_path); }; Some(()) } diff --git a/src/model/config/keybinding.rs b/src/model/config/keybinding.rs index 17972bf..1878e7a 100644 --- a/src/model/config/keybinding.rs +++ b/src/model/config/keybinding.rs @@ -24,6 +24,8 @@ pub struct Keybinding { pub rename_mode: String, #[serde(default = "Keybinding::default_remove")] pub remove: String, + #[serde(default = "Keybinding::default_new_entry")] + pub new_entry: String, } impl Default for Keybinding { @@ -38,6 +40,7 @@ impl Default for Keybinding { reload: Self::default_reload(), rename_mode: Self::default_rename_mode(), remove: Self::default_remove(), + new_entry: Self::default_new_entry(), } } } @@ -70,4 +73,7 @@ impl Keybinding { fn default_remove() -> String { String::from("ctrl+d") } + fn default_new_entry() -> String { + String::from("n") + } } diff --git a/src/model/config/mod.rs b/src/model/config/mod.rs index a0b77bf..895e942 100644 --- a/src/model/config/mod.rs +++ b/src/model/config/mod.rs @@ -80,6 +80,9 @@ impl Config { "--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.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.reload" => config.keybinding.reload = Self::parse_value((key, value)), "--setup.working_dir" => config.setup.working_dir = Self::parse_value((key, value)), diff --git a/src/view/mod.rs b/src/view/mod.rs index 83c3afa..ce9d776 100644 --- a/src/view/mod.rs +++ b/src/view/mod.rs @@ -15,17 +15,36 @@ mod print; mod scroll; mod update; +pub struct CurrentEntry { + pub path: PathBuf, + pub is_expanded: bool, +} + pub struct Pager { config: Config, pub mode: Mode, pub cursor_row: i32, - pub current_entry: PathBuf, + pub current_entry: CurrentEntry, out: W, terminal_cols: i32, terminal_rows: i32, text_row: i32, } +impl CurrentEntry { + fn new(path: impl AsRef) -> 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 Pager { pub fn new(config: Config, mut out: W) -> Self { info!("initializing pager"); @@ -40,7 +59,7 @@ impl Pager { .unwrap(); Self { - current_entry: Path::new(&config.setup.working_dir).to_path_buf(), + current_entry: CurrentEntry::new(&config.setup.working_dir), config, cursor_row: 0, out, diff --git a/src/view/mode.rs b/src/view/mode.rs index 037eed1..ac885b0 100644 --- a/src/view/mode.rs +++ b/src/view/mode.rs @@ -8,6 +8,8 @@ pub enum Mode { Normal, /// Rename mode. Renaming a file. Rename(String), + /// Create entry mode. Creating a new file and directory. + CreateEntry, } impl Mode { diff --git a/src/view/update.rs b/src/view/update.rs index 1bc3c6c..755fdb8 100644 --- a/src/view/update.rs +++ b/src/view/update.rs @@ -6,6 +6,8 @@ use crate::{controller::Entrie, view::Pager}; use std::{io::Write, path}; use termion::terminal_size; +use super::CurrentEntry; + impl Pager { fn update_terminal_size(&mut self) { let (terminal_cols_raw, terminal_rows_raw) = terminal_size().unwrap(); @@ -59,8 +61,12 @@ impl Pager { let entry = &entries[index as usize]; if index == self.cursor_row { - self.current_entry = - path::absolute(&entry.path).unwrap_or_else(|_| entry.path.clone()); + self.current_entry = CurrentEntry::new( + 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 filename = entry.path.file_name().unwrap().to_str().unwrap(); diff --git a/twilight-tree.toml b/twilight-tree.toml index 07307a0..9682a87 100644 --- a/twilight-tree.toml +++ b/twilight-tree.toml @@ -52,6 +52,7 @@ reload = "ctrl+r" skip_up = "ctrl+up" skip_down = "ctrl+down" remove = "d" +new_entry = "n" [setup] # the working directory used when starting