Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add marking and cancelling for file operations #500

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions config/keymap.toml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ keymap = [
{ keys = [" "], commands = ["select --toggle=true"] },
{ keys = ["t"], commands = ["select --all=true --toggle=true"] },
{ keys = ["V"], commands = ["toggle_visual"] },
{ keys = ["ctrl+v"], commands = ["select --all=true --deselect=true"] },
{ keys = ["alt+v"], commands = ["cancel_file_operation"] },

{ keys = ["w"], commands = ["show_tasks --exit-key=w"] },
{ keys = ["b", "b"], commands = ["bulk_rename"] },
Expand Down
19 changes: 17 additions & 2 deletions config/theme.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,31 @@ bold = true

# Selected files (standard selection)
[selection]
fg = "light_yellow"
fg = "light_green"
bold = true
prefix = " "

# Files selected in current visual mode
[visual_mode_selection]
fg = "light_red"
fg = "light_yellow"
bold = true
prefix = "v "

[mark.cut]
fg = "red"
bold = true
# prefix = " "

[mark.copy]
fg = "rgb(235, 104, 65)" # orange
bold = true
# prefix = " "

[mark.symlink]
fg = "rgb(254, 132, 172)" # pink
bold = true
# prefix = " "

##########################################
## File List - System File Types
##########################################
Expand Down
8 changes: 5 additions & 3 deletions docs/configuration/keymap.toml.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,8 @@ All methods (except `reverse`) support the `--reverse` flag:

### `paste_files`: move/copy files stored from a previous `cut_files` or `copy_files` command

### `cancel_file_operation`: unmark and cancel the current file operation

### `delete_files`: delete selected files (or current file if none were selected).

- `--foreground=true`: will delete files in the foreground
Expand Down Expand Up @@ -378,7 +380,7 @@ This command has the same options for `select`. Notice that it's necessary to qu

### `select_fzf`: select files in the current directory via fzf

This command has the same options for `select`. Use tab to select or deselect files in fzf.
This command has the same options for `select`. Use tab to select or deselect files in fzf.

### `filter`: filter the current directory list.

Expand Down Expand Up @@ -407,7 +409,7 @@ When disabling, the current “visual mode selection” is turned into normal se
- `--type=string`: change configurations of operations using substring matching
- `--type=glob`: change configurations of operations using glob matching
- `--type=regex`: change configurations of operations using regex
- `--type=fzf`: change configurations of operations using fzf
- `--type=fzf`: change configurations of operations using fzf
- when no option is added, type is set to `string` by default
- Value
- `insensitive`
Expand All @@ -426,7 +428,7 @@ Define search command using [`custom_command`]()

### `custom_search_interactive`

Similar to `select` and `custom_search`. Allows user to execute `custom_command` and
Similar to `select` and `custom_search`. Allows user to execute `custom_command` and
then interactively operate on the results.

## Bookmarks
Expand Down
1 change: 1 addition & 0 deletions src/commands/delete_files.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ fn delete_files(
overwrite: false,
skip_exist: false,
permanently: !context.config_ref().use_trash || permanently,
cancel: false,
};

let dest = path::PathBuf::new();
Expand Down
100 changes: 95 additions & 5 deletions src/commands/file_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ use std::process::{Command, Stdio};

use crate::context::{AppContext, LocalStateContext};
use crate::error::{AppError, AppErrorKind, AppResult};
use crate::fs::JoshutoDirList;
use crate::io::{FileOperation, FileOperationOptions, IoWorkerThread};

fn new_local_state(context: &mut AppContext, file_op: FileOperation) -> Option<()> {
let list = context.tab_context_ref().curr_tab_ref().curr_list_ref()?;
let selected = list.get_selected_paths();

let mut local_state = LocalStateContext::new();
local_state.set_paths(selected.into_iter());
local_state.set_file_op(file_op);
Expand All @@ -16,29 +16,119 @@ fn new_local_state(context: &mut AppContext, file_op: FileOperation) -> Option<(
Some(())
}

fn mark_entries(context: &mut AppContext, op: FileOperation) {
let tab = context.tab_context_mut().curr_tab_mut();

if let Some(curr_list) = tab.curr_list_mut() {
curr_list.iter_mut().for_each(|entry| {
entry.set_mark_cut_selected(false);
entry.set_mark_copy_selected(false);
entry.set_mark_sym_selected(false);
});

match curr_list.selected_count() {
count if count != 0 => {
curr_list.iter_mut().for_each(|entry| match op {
FileOperation::Cut if entry.is_permanent_selected() => {
entry.set_mark_cut_selected(true)
}
FileOperation::Copy if entry.is_permanent_selected() => {
entry.set_mark_copy_selected(true)
}
FileOperation::Symlink { .. } if entry.is_permanent_selected() => {
entry.set_mark_sym_selected(true)
}
_ => {}
});
}
_ => {
if let Some(entry) = curr_list.curr_entry_mut() {
match op {
FileOperation::Cut => entry.set_mark_cut_selected(true),
FileOperation::Copy => entry.set_mark_copy_selected(true),
FileOperation::Symlink { .. } => entry.set_mark_sym_selected(true),
_ => {}
}
}
}
}
}
}

fn unmark_entries(curr_tab: &mut JoshutoDirList) {
if curr_tab.selected_count() != 0 {
curr_tab.iter_mut().for_each(|entry| {
if entry.is_marked_cut() {
entry.set_mark_cut_selected(false)
} else if entry.is_marked_copy() {
entry.set_mark_copy_selected(false)
} else if entry.is_marked_sym() {
entry.set_mark_sym_selected(false)
}
})
} else if let Some(entry) = curr_tab.curr_entry_mut() {
if entry.is_marked_cut() {
entry.set_mark_cut_selected(false)
} else if entry.is_marked_copy() {
entry.set_mark_copy_selected(false)
} else if entry.is_marked_sym() {
entry.set_mark_sym_selected(false)
}
}
}

fn unmark_and_cancel_all(context: &mut AppContext) -> AppResult {
context.tab_context_mut().iter_mut().for_each(|entry| {
if let Some(curr_list) = entry.1.curr_list_mut() {
unmark_entries(curr_list);
}
if let Some(par_list) = entry.1.parent_list_mut() {
unmark_entries(par_list);
}
if let Some(child_list) = entry.1.child_list_mut() {
unmark_entries(child_list);
}
});

Err(AppError::new(
AppErrorKind::Io,
"File operation cancelled!".to_string(),
))
}

fn perform_file_operation(context: &mut AppContext, op: FileOperation) -> AppResult {
mark_entries(context, op);
new_local_state(context, op);
Ok(())
}

pub fn cut(context: &mut AppContext) -> AppResult {
new_local_state(context, FileOperation::Cut);
perform_file_operation(context, FileOperation::Cut)?;
Ok(())
}

pub fn copy(context: &mut AppContext) -> AppResult {
new_local_state(context, FileOperation::Copy);
perform_file_operation(context, FileOperation::Copy)?;
Ok(())
}

pub fn symlink_absolute(context: &mut AppContext) -> AppResult {
new_local_state(context, FileOperation::Symlink { relative: false });
perform_file_operation(context, FileOperation::Symlink { relative: false })?;
Ok(())
}

pub fn symlink_relative(context: &mut AppContext) -> AppResult {
new_local_state(context, FileOperation::Symlink { relative: true });
perform_file_operation(context, FileOperation::Symlink { relative: true })?;
Ok(())
}

pub fn paste(context: &mut AppContext, options: FileOperationOptions) -> AppResult {
match context.take_local_state() {
Some(state) if !state.paths.is_empty() => {
if options.cancel {
unmark_and_cancel_all(context)?;
}

let dest = context.tab_context_ref().curr_tab_ref().cwd().to_path_buf();
let worker_thread = IoWorkerThread::new(state.file_op, state.paths, dest, options);
context.worker_context_mut().push_worker(worker_thread);
Expand Down
3 changes: 2 additions & 1 deletion src/commands/reload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ pub fn reload(context: &mut AppContext, id: &Uuid) -> std::io::Result<()> {
}

pub fn reload_dirlist(context: &mut AppContext) -> AppResult {
reload(context, &context.tab_context_ref().curr_tab_id())?;
let curr_tab_id = context.tab_context_ref().curr_tab_id();
reload(context, &curr_tab_id)?;
Ok(())
}
23 changes: 23 additions & 0 deletions src/commands/tab_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,29 @@ pub fn reload_all_tabs(context: &mut AppContext, curr_path: &Path) -> io::Result
Ok(())
}

pub fn reload_all_tabs_no_preserve(context: &mut AppContext, curr_path: &Path) -> io::Result<()> {
let mut map = HashMap::new();
{
let mut display_options = context.config_ref().display_options_clone();
display_options._preserve_selection = false;

for (id, tab) in context.tab_context_ref().iter() {
let tab_options = tab.option_ref();
let history = tab.history_ref();
let dirlist =
create_dirlist_with_history(history, curr_path, &display_options, tab_options)?;
map.insert(*id, dirlist);
}
}

for (id, dirlist) in map {
if let Some(tab) = context.tab_context_mut().tab_mut(&id) {
tab.history_mut().insert(curr_path.to_path_buf(), dirlist);
}
}
Ok(())
}

pub fn remove_entry_from_all_tabs(context: &mut AppContext, curr_path: &Path) {
for (_, tab) in context.tab_context_mut().iter_mut() {
tab.history_mut().remove(curr_path);
Expand Down
3 changes: 3 additions & 0 deletions src/config/clean/app/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ impl AppConfig {
pub fn display_options_mut(&mut self) -> &mut DisplayOption {
&mut self._display_options
}
pub fn display_options_clone(&self) -> DisplayOption {
self._display_options.clone()
}

pub fn preview_options_ref(&self) -> &PreviewOption {
&self._preview_options
Expand Down
3 changes: 3 additions & 0 deletions src/config/clean/app/display/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub struct DisplayOption {
pub _show_borders: bool,
pub _show_hidden: bool,
pub _show_icons: bool,
pub _preserve_selection: bool,
pub _line_nums: LineNumberStyle,
pub column_ratio: (usize, usize, usize),
pub default_layout: [Constraint; 3],
Expand Down Expand Up @@ -70,6 +71,7 @@ impl From<DisplayOptionRaw> for DisplayOption {
_show_borders: raw.show_borders,
_show_hidden: raw.show_hidden,
_show_icons: raw.show_icons,
_preserve_selection: raw.preserve_selection,
_line_nums,

column_ratio,
Expand Down Expand Up @@ -158,6 +160,7 @@ impl std::default::Default for DisplayOption {
_show_borders: true,
_show_hidden: false,
_show_icons: false,
_preserve_selection: true,
_line_nums: LineNumberStyle::None,
default_layout,
no_preview_layout,
Expand Down
10 changes: 10 additions & 0 deletions src/config/clean/theme/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub struct AppTheme {
pub regular: AppStyle,
pub selection: AppStyle,
pub visual_mode_selection: AppStyle,
pub mark: HashMap<String, AppStyle>,
pub directory: AppStyle,
pub executable: AppStyle,
pub link: AppStyle,
Expand Down Expand Up @@ -52,6 +53,14 @@ impl From<AppThemeRaw> for AppTheme {
let tabs = raw.tabs;
let selection = raw.selection.to_style_theme();
let visual_mode_selection = raw.visual_mode_selection.to_style_theme();
let mark: HashMap<String, AppStyle> = raw
.mark
.iter()
.map(|(k, v)| {
let style = v.to_style_theme();
(k.clone(), style)
})
.collect();
let executable = raw.executable.to_style_theme();
let regular = raw.regular.to_style_theme();
let directory = raw.directory.to_style_theme();
Expand All @@ -77,6 +86,7 @@ impl From<AppThemeRaw> for AppTheme {
Self {
selection,
visual_mode_selection,
mark,
executable,
regular,
directory,
Expand Down
4 changes: 4 additions & 0 deletions src/config/raw/app/display/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ pub struct DisplayOptionRaw {
#[serde(default)]
pub show_icons: bool,

#[serde(default = "default_true")]
pub preserve_selection: bool,

#[serde(default, rename = "sort")]
pub sort_options: SortOptionRaw,

Expand All @@ -63,6 +66,7 @@ impl std::default::Default for DisplayOptionRaw {
show_borders: true,
show_hidden: false,
show_icons: false,
preserve_selection: true,
sort_options: SortOptionRaw::default(),
line_number_style: "none".to_string(),
linemode: LineMode::default(),
Expand Down
2 changes: 2 additions & 0 deletions src/config/raw/theme/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ pub struct AppThemeRaw {
#[serde(default)]
pub visual_mode_selection: AppStyleRaw,
#[serde(default)]
pub mark: HashMap<String, AppStyleRaw>,
#[serde(default)]
pub directory: AppStyleRaw,
#[serde(default)]
pub executable: AppStyleRaw,
Expand Down
4 changes: 2 additions & 2 deletions src/event/process_event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,14 @@ pub fn process_finished_worker(context: &mut AppContext, res: AppResult<FileOper

let observer_path = observer.dest_path();
if observer_path.exists() {
let _ = tab_ops::reload_all_tabs(context, observer_path);
let _ = tab_ops::reload_all_tabs_no_preserve(context, observer_path);
} else {
tab_ops::remove_entry_from_all_tabs(context, observer_path);
}

let observer_path = observer.src_path();
if observer_path.exists() {
let _ = tab_ops::reload_all_tabs(context, observer_path);
let _ = tab_ops::reload_all_tabs_no_preserve(context, observer_path);
} else {
tab_ops::remove_entry_from_all_tabs(context, observer_path);
}
Expand Down
10 changes: 10 additions & 0 deletions src/fs/dirlist.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,16 @@ impl JoshutoDirList {
self.contents.iter().filter(|e| e.is_selected()).count()
}

pub fn marked_cut_count(&self) -> usize {
self.contents.iter().filter(|e| e.is_marked_cut()).count()
}
pub fn marked_copy_count(&self) -> usize {
self.contents.iter().filter(|e| e.is_marked_copy()).count()
}
pub fn marked_sym_count(&self) -> usize {
self.contents.iter().filter(|e| e.is_marked_sym()).count()
}

pub fn iter_selected(&self) -> impl Iterator<Item = &JoshutoDirEntry> {
self.contents.iter().filter(|entry| entry.is_selected())
}
Expand Down
Loading
Loading