Skip to content

Commit a225836

Browse files
committed
feat: add marking and cancelling for file operations
Implement marking functionality with different colors for file op, and the ability to clear marks, allowing for the cancellation of file operations. { keys = ["alt+v"], commands = ["cancel_file_operation"] }, [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 = " "
1 parent 59b6ae5 commit a225836

25 files changed

+327
-39
lines changed

config/keymap.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ keymap = [
9090
{ keys = [" "], commands = ["select --toggle=true"] },
9191
{ keys = ["t"], commands = ["select --all=true --toggle=true"] },
9292
{ keys = ["V"], commands = ["toggle_visual"] },
93+
{ keys = ["ctrl+v"], commands = ["select --all=true --deselect=true"] },
94+
{ keys = ["alt+v"], commands = ["cancel_file_operation"] },
9395

9496
{ keys = ["w"], commands = ["show_tasks --exit-key=w"] },
9597
{ keys = ["b", "b"], commands = ["bulk_rename"] },

config/theme.toml

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,31 @@ bold = true
3838

3939
# Selected files (standard selection)
4040
[selection]
41-
fg = "light_yellow"
41+
fg = "light_green"
4242
bold = true
4343
prefix = " "
4444

4545
# Files selected in current visual mode
4646
[visual_mode_selection]
47-
fg = "light_red"
47+
fg = "light_yellow"
4848
bold = true
4949
prefix = "v "
5050

51+
[mark.cut]
52+
fg = "red"
53+
bold = true
54+
# prefix = " "
55+
56+
[mark.copy]
57+
fg = "rgb(235, 104, 65)" # orange
58+
bold = true
59+
# prefix = " "
60+
61+
[mark.symlink]
62+
fg = "rgb(254, 132, 172)" # pink
63+
bold = true
64+
# prefix = " "
65+
5166
##########################################
5267
## File List - System File Types
5368
##########################################

docs/configuration/keymap.toml.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,8 @@ function joshuto() {
271271

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

274+
### `cancel_file_operation`: unmark and cancel the current file operation
275+
274276
### `delete_files`: delete selected files (or current file if none were selected).
275277

276278
- `--foreground=true`: will delete files in the foreground
@@ -369,7 +371,7 @@ This command has the same options for `select`. Notice that it's necessary to qu
369371

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

372-
This command has the same options for `select`. Use tab to select or deselect files in fzf.
374+
This command has the same options for `select`. Use tab to select or deselect files in fzf.
373375

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

@@ -398,7 +400,7 @@ When disabling, the current “visual mode selection” is turned into normal se
398400
- `--type=string`: change configurations of operations using substring matching
399401
- `--type=glob`: change configurations of operations using glob matching
400402
- `--type=regex`: change configurations of operations using regex
401-
- `--type=fzf`: change configurations of operations using fzf
403+
- `--type=fzf`: change configurations of operations using fzf
402404
- when no option is added, type is set to `string` by default
403405
- Value
404406
- `insensitive`
@@ -417,7 +419,7 @@ Define search command using [`custom_command`]()
417419

418420
### `custom_search_interactive`
419421

420-
Similar to `select` and `custom_search`. Allows user to execute `custom_command` and
422+
Similar to `select` and `custom_search`. Allows user to execute `custom_command` and
421423
then interactively operate on the results.
422424

423425
## Bookmarks

src/commands/delete_files.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ fn delete_files(
4747
overwrite: false,
4848
skip_exist: false,
4949
permanently: !context.config_ref().use_trash || permanently,
50+
cancel: false,
5051
};
5152

5253
let dest = path::PathBuf::new();

src/commands/file_ops.rs

Lines changed: 95 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ use std::process::{Command, Stdio};
33

44
use crate::context::{AppContext, LocalStateContext};
55
use crate::error::{AppError, AppErrorKind, AppResult};
6+
use crate::fs::JoshutoDirList;
67
use crate::io::{FileOperation, FileOperationOptions, IoWorkerThread};
78

89
fn new_local_state(context: &mut AppContext, file_op: FileOperation) -> Option<()> {
910
let list = context.tab_context_ref().curr_tab_ref().curr_list_ref()?;
1011
let selected = list.get_selected_paths();
11-
1212
let mut local_state = LocalStateContext::new();
1313
local_state.set_paths(selected.into_iter());
1414
local_state.set_file_op(file_op);
@@ -17,29 +17,119 @@ fn new_local_state(context: &mut AppContext, file_op: FileOperation) -> Option<(
1717
Some(())
1818
}
1919

20+
fn mark_entries(context: &mut AppContext, op: FileOperation) {
21+
let tab = context.tab_context_mut().curr_tab_mut();
22+
23+
if let Some(curr_list) = tab.curr_list_mut() {
24+
curr_list.iter_mut().for_each(|entry| {
25+
entry.set_mark_cut_selected(false);
26+
entry.set_mark_copy_selected(false);
27+
entry.set_mark_sym_selected(false);
28+
});
29+
30+
match curr_list.selected_count() {
31+
count if count != 0 => {
32+
curr_list.iter_mut().for_each(|entry| match op {
33+
FileOperation::Cut if entry.is_permanent_selected() => {
34+
entry.set_mark_cut_selected(true)
35+
}
36+
FileOperation::Copy if entry.is_permanent_selected() => {
37+
entry.set_mark_copy_selected(true)
38+
}
39+
FileOperation::Symlink { .. } if entry.is_permanent_selected() => {
40+
entry.set_mark_sym_selected(true)
41+
}
42+
_ => {}
43+
});
44+
}
45+
_ => {
46+
if let Some(entry) = curr_list.curr_entry_mut() {
47+
match op {
48+
FileOperation::Cut => entry.set_mark_cut_selected(true),
49+
FileOperation::Copy => entry.set_mark_copy_selected(true),
50+
FileOperation::Symlink { .. } => entry.set_mark_sym_selected(true),
51+
_ => {}
52+
}
53+
}
54+
}
55+
}
56+
}
57+
}
58+
59+
fn unmark_entries(curr_tab: &mut JoshutoDirList) {
60+
if curr_tab.selected_count() != 0 {
61+
curr_tab.iter_mut().for_each(|entry| {
62+
if entry.is_marked_cut() {
63+
entry.set_mark_cut_selected(false)
64+
} else if entry.is_marked_copy() {
65+
entry.set_mark_copy_selected(false)
66+
} else if entry.is_marked_sym() {
67+
entry.set_mark_sym_selected(false)
68+
}
69+
})
70+
} else if let Some(entry) = curr_tab.curr_entry_mut() {
71+
if entry.is_marked_cut() {
72+
entry.set_mark_cut_selected(false)
73+
} else if entry.is_marked_copy() {
74+
entry.set_mark_copy_selected(false)
75+
} else if entry.is_marked_sym() {
76+
entry.set_mark_sym_selected(false)
77+
}
78+
}
79+
}
80+
81+
fn unmark_and_cancel_all(context: &mut AppContext) -> AppResult {
82+
context.tab_context_mut().iter_mut().for_each(|entry| {
83+
if let Some(curr_list) = entry.1.curr_list_mut() {
84+
unmark_entries(curr_list);
85+
}
86+
if let Some(par_list) = entry.1.parent_list_mut() {
87+
unmark_entries(par_list);
88+
}
89+
if let Some(child_list) = entry.1.child_list_mut() {
90+
unmark_entries(child_list);
91+
}
92+
});
93+
94+
Err(AppError::new(
95+
AppErrorKind::Io(io::ErrorKind::Interrupted),
96+
"File operation cancelled!".to_string(),
97+
))
98+
}
99+
100+
fn perform_file_operation(context: &mut AppContext, op: FileOperation) -> AppResult {
101+
mark_entries(context, op);
102+
new_local_state(context, op);
103+
Ok(())
104+
}
105+
20106
pub fn cut(context: &mut AppContext) -> AppResult {
21-
new_local_state(context, FileOperation::Cut);
107+
perform_file_operation(context, FileOperation::Cut)?;
22108
Ok(())
23109
}
24110

25111
pub fn copy(context: &mut AppContext) -> AppResult {
26-
new_local_state(context, FileOperation::Copy);
112+
perform_file_operation(context, FileOperation::Copy)?;
27113
Ok(())
28114
}
29115

30116
pub fn symlink_absolute(context: &mut AppContext) -> AppResult {
31-
new_local_state(context, FileOperation::Symlink { relative: false });
117+
perform_file_operation(context, FileOperation::Symlink { relative: false })?;
32118
Ok(())
33119
}
34120

35121
pub fn symlink_relative(context: &mut AppContext) -> AppResult {
36-
new_local_state(context, FileOperation::Symlink { relative: true });
122+
perform_file_operation(context, FileOperation::Symlink { relative: true })?;
37123
Ok(())
38124
}
39125

40126
pub fn paste(context: &mut AppContext, options: FileOperationOptions) -> AppResult {
41127
match context.take_local_state() {
42128
Some(state) if !state.paths.is_empty() => {
129+
if options.cancel {
130+
unmark_and_cancel_all(context)?;
131+
}
132+
43133
let dest = context.tab_context_ref().curr_tab_ref().cwd().to_path_buf();
44134
let worker_thread = IoWorkerThread::new(state.file_op, state.paths, dest, options);
45135
context.worker_context_mut().push_worker(worker_thread);

src/commands/reload.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ pub fn reload(context: &mut AppContext, id: &Uuid) -> std::io::Result<()> {
8484
}
8585

8686
pub fn reload_dirlist(context: &mut AppContext) -> AppResult {
87-
reload(context, &context.tab_context_ref().curr_tab_id())?;
87+
let curr_tab_id = context.tab_context_ref().curr_tab_id();
88+
reload(context, &curr_tab_id)?;
8889
Ok(())
8990
}

src/commands/tab_ops.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -209,13 +209,14 @@ pub fn close_tab(context: &mut AppContext) -> AppResult {
209209
pub fn reload_all_tabs(context: &mut AppContext, curr_path: &Path) -> io::Result<()> {
210210
let mut map = HashMap::new();
211211
{
212-
let display_options = context.config_ref().display_options_ref();
212+
let mut display_options = context.config_ref().display_options_clone();
213+
display_options._preserve_selection = false;
213214

214215
for (id, tab) in context.tab_context_ref().iter() {
215216
let tab_options = tab.option_ref();
216217
let history = tab.history_ref();
217218
let dirlist =
218-
create_dirlist_with_history(history, curr_path, display_options, tab_options)?;
219+
create_dirlist_with_history(history, curr_path, &display_options, tab_options)?;
219220
map.insert(*id, dirlist);
220221
}
221222
}

src/config/clean/app/config.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ impl AppConfig {
4242
pub fn display_options_mut(&mut self) -> &mut DisplayOption {
4343
&mut self._display_options
4444
}
45+
pub fn display_options_clone(&self) -> DisplayOption {
46+
self._display_options.clone()
47+
}
4548

4649
pub fn preview_options_ref(&self) -> &PreviewOption {
4750
&self._preview_options

src/config/clean/app/display/config.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ pub struct DisplayOption {
2626
pub _show_borders: bool,
2727
pub _show_hidden: bool,
2828
pub _show_icons: bool,
29+
pub _preserve_selection: bool,
2930
pub _line_nums: LineNumberStyle,
3031
pub column_ratio: (usize, usize, usize),
3132
pub default_layout: [Constraint; 3],
@@ -70,6 +71,7 @@ impl From<DisplayOptionRaw> for DisplayOption {
7071
_show_borders: raw.show_borders,
7172
_show_hidden: raw.show_hidden,
7273
_show_icons: raw.show_icons,
74+
_preserve_selection: raw.preserve_selection,
7375
_line_nums,
7476

7577
column_ratio,
@@ -158,6 +160,7 @@ impl std::default::Default for DisplayOption {
158160
_show_borders: true,
159161
_show_hidden: false,
160162
_show_icons: false,
163+
_preserve_selection: true,
161164
_line_nums: LineNumberStyle::None,
162165
default_layout,
163166
no_preview_layout,

src/config/clean/theme/config.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pub struct AppTheme {
1515
pub regular: AppStyle,
1616
pub selection: AppStyle,
1717
pub visual_mode_selection: AppStyle,
18+
pub mark: HashMap<String, AppStyle>,
1819
pub directory: AppStyle,
1920
pub executable: AppStyle,
2021
pub link: AppStyle,
@@ -52,6 +53,14 @@ impl From<AppThemeRaw> for AppTheme {
5253
let tabs = raw.tabs;
5354
let selection = raw.selection.to_style_theme();
5455
let visual_mode_selection = raw.visual_mode_selection.to_style_theme();
56+
let mark: HashMap<String, AppStyle> = raw
57+
.mark
58+
.iter()
59+
.map(|(k, v)| {
60+
let style = v.to_style_theme();
61+
(k.clone(), style)
62+
})
63+
.collect();
5564
let executable = raw.executable.to_style_theme();
5665
let regular = raw.regular.to_style_theme();
5766
let directory = raw.directory.to_style_theme();
@@ -77,6 +86,7 @@ impl From<AppThemeRaw> for AppTheme {
7786
Self {
7887
selection,
7988
visual_mode_selection,
89+
mark,
8090
executable,
8191
regular,
8292
directory,

src/config/raw/app/display/config.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ pub struct DisplayOptionRaw {
4242
#[serde(default)]
4343
pub show_icons: bool,
4444

45+
#[serde(default = "default_true")]
46+
pub preserve_selection: bool,
47+
4548
#[serde(default = "default_true")]
4649
pub tilde_in_titlebar: bool,
4750

@@ -66,6 +69,7 @@ impl std::default::Default for DisplayOptionRaw {
6669
show_borders: true,
6770
show_hidden: false,
6871
show_icons: false,
72+
preserve_selection: true,
6973
sort_options: SortOptionRaw::default(),
7074
tilde_in_titlebar: true,
7175
line_number_style: "none".to_string(),

src/config/raw/theme/config.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ pub struct AppThemeRaw {
1515
#[serde(default)]
1616
pub visual_mode_selection: AppStyleRaw,
1717
#[serde(default)]
18+
pub mark: HashMap<String, AppStyleRaw>,
19+
#[serde(default)]
1820
pub directory: AppStyleRaw,
1921
#[serde(default)]
2022
pub executable: AppStyleRaw,

src/fs/dirlist.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,16 @@ impl JoshutoDirList {
222222
self.contents.iter().filter(|e| e.is_selected()).count()
223223
}
224224

225+
pub fn marked_cut_count(&self) -> usize {
226+
self.contents.iter().filter(|e| e.is_marked_cut()).count()
227+
}
228+
pub fn marked_copy_count(&self) -> usize {
229+
self.contents.iter().filter(|e| e.is_marked_copy()).count()
230+
}
231+
pub fn marked_sym_count(&self) -> usize {
232+
self.contents.iter().filter(|e| e.is_marked_sym()).count()
233+
}
234+
225235
pub fn iter_selected(&self) -> impl Iterator<Item = &JoshutoDirEntry> {
226236
self.contents.iter().filter(|entry| entry.is_selected())
227237
}

0 commit comments

Comments
 (0)