feat: Support creating new entries
Signed-off-by: Awiteb <a@4rs.nl>
This commit is contained in:
parent
6acffbf9e1
commit
626b4f8cc5
12 changed files with 113 additions and 14 deletions
|
@ -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 |
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
44
src/controller/key_event_matcher/new_entry.rs
Normal file
44
src/controller/key_event_matcher/new_entry.rs
Normal 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(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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(())
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)),
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue