summaryrefslogtreecommitdiffstats
path: root/src/app/status.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/app/status.rs')
-rw-r--r--src/app/status.rs362
1 files changed, 244 insertions, 118 deletions
diff --git a/src/app/status.rs b/src/app/status.rs
index 823aaa5..a636e02 100644
--- a/src/app/status.rs
+++ b/src/app/status.rs
@@ -10,6 +10,7 @@ use tuikit::prelude::{from_keyname, Event};
use tuikit::term::Term;
use crate::app::ClickableLine;
+use crate::app::FlaggedHeader;
use crate::app::Footer;
use crate::app::Header;
use crate::app::InternalSettings;
@@ -27,10 +28,8 @@ use crate::io::{
execute_and_capture_output_without_check, execute_sudo_command_with_password,
reset_sudo_faillock,
};
-use crate::modes::CopyMove;
use crate::modes::Display;
use crate::modes::Edit;
-use crate::modes::FileKind;
use crate::modes::InputSimple;
use crate::modes::IsoDevice;
use crate::modes::Menu;
@@ -41,13 +40,15 @@ use crate::modes::PasswordKind;
use crate::modes::PasswordUsage;
use crate::modes::Permissions;
use crate::modes::Preview;
-use crate::modes::SelectableContent;
+use crate::modes::Selectable;
use crate::modes::ShellCommandParser;
use crate::modes::Skimer;
use crate::modes::Tree;
use crate::modes::Users;
use crate::modes::{copy_move, regex_matcher};
use crate::modes::{BlockDeviceAction, Navigate};
+use crate::modes::{Content, FileInfo};
+use crate::modes::{ContentWindow, CopyMove};
use crate::{log_info, log_line};
pub enum Window {
@@ -86,7 +87,7 @@ impl Status {
/// Creates a new status for the application.
/// It requires most of the information (arguments, configuration, height
/// of the terminal, the formated help string).
- pub fn new(height: usize, term: Arc<Term>, opener: Opener) -> Result<Self> {
+ pub fn new(height: usize, term: Arc<Term>, opener: Opener, binds: &Bindings) -> Result<Self> {
let skimer = None;
let index = 0;
@@ -101,7 +102,7 @@ impl Status {
let display_settings = Session::new(term.term_size()?.0);
let mut internal_settings = InternalSettings::new(opener, term, sys);
let mount_points = internal_settings.mount_points();
- let menu = Menu::new(start_dir, &mount_points)?;
+ let menu = Menu::new(start_dir, &mount_points, binds)?;
let users_left = Users::new();
let users_right = users_left.clone();
@@ -168,13 +169,12 @@ impl Status {
Ok(())
}
- pub fn window_from_row(&self, row: u16, height: usize) -> Window {
+ fn window_from_row(&self, row: u16, height: usize) -> Window {
let win_height = if matches!(self.current_tab().edit_mode, Edit::Nothing) {
height
} else {
height / 2
};
- log_info!("clicked row {row}, height {height}, win_height {win_height}");
let w_index = row as usize / win_height;
if w_index == 1 {
Window::Menu
@@ -187,30 +187,39 @@ impl Status {
}
}
+ /// Execute a click at `row`, `col`. Action depends on which window was clicked.
pub fn click(&mut self, row: u16, col: u16, binds: &Bindings) -> Result<()> {
let window = self.window_from_row(row, self.term_size()?.1);
self.select_tab_from_col(col)?;
- let (_, current_height) = self.term_size()?;
match window {
- Window::Menu => self.menu_action(row, current_height),
- Window::Header => self.header_action(col, binds)?,
- Window::Footer => self.footer_action(col, binds)?,
+ Window::Menu => self.menu_action(row),
+ Window::Header => self.header_action(col, binds),
+ Window::Footer => self.footer_action(col, binds),
Window::Files => {
- self.current_tab_mut().select_row(row, current_height)?;
- self.update_second_pane_for_preview()?;
+ if matches!(self.current_tab().display_mode, Display::Flagged) {
+ self.menu.flagged.select_row(row)
+ } else {
+ self.current_tab_mut().select_row(row)?
+ }
+ self.update_second_pane_for_preview()
}
- };
- Ok(())
+ }
+ }
+
+ pub fn second_window_height(&self) -> Result<usize> {
+ let (_, height) = self.term_size()?;
+ Ok(height / 2 + (height % 2))
}
- fn menu_action(&mut self, row: u16, height: usize) {
- let second_window_height = height / 2 + (height % 2);
+ /// Execute a click on a menu item. Action depends on which menu was opened.
+ fn menu_action(&mut self, row: u16) -> Result<()> {
+ let second_window_height = self.second_window_height()?;
let offset = row as usize - second_window_height;
if offset >= 4 {
- let index = offset - 4;
+ let index = offset - 4 + self.menu.window.top;
match self.current_tab().edit_mode {
Edit::Navigate(navigate) => match navigate {
- Navigate::Bulk => self.menu.bulk_set_index(index),
+ Navigate::BulkMenu => self.menu.bulk.set_index(index),
Navigate::CliApplication => self.menu.cli_applications.set_index(index),
Navigate::Compress => self.menu.compression.set_index(index),
Navigate::Context => self.menu.context.set_index(index),
@@ -218,7 +227,7 @@ impl Status {
Navigate::History => self.current_tab_mut().history.set_index(index),
Navigate::Jump => self.menu.flagged.set_index(index),
Navigate::Marks(_) => self.menu.marks.set_index(index),
- Navigate::RemovableDevices => self.menu.removable_set_index(index),
+ Navigate::RemovableDevices => self.menu.removable_devices.set_index(index),
Navigate::Shortcut => self.menu.shortcut.set_index(index),
Navigate::Trash => self.menu.trash.set_index(index),
Navigate::TuiApplication => self.menu.tui_applications.set_index(index),
@@ -226,13 +235,17 @@ impl Status {
Edit::InputCompleted(_) => self.menu.completion.set_index(index),
_ => (),
}
+ self.menu.window.scroll_to(index);
}
+ Ok(())
}
+ /// Select the left tab
pub fn select_left(&mut self) {
self.index = 0;
}
+ /// Select the right tab
pub fn select_right(&mut self) {
self.index = 1;
}
@@ -243,8 +256,11 @@ impl Status {
/// There's surelly a better way, like doing it only once in a while or on
/// demand.
pub fn refresh_shortcuts(&mut self) {
- self.menu
- .refresh_shortcuts(&self.internal_settings.mount_points());
+ self.menu.refresh_shortcuts(
+ &self.internal_settings.mount_points(),
+ self.tabs[0].current_path(),
+ self.tabs[1].current_path(),
+ );
}
/// Returns an array of Disks
@@ -264,7 +280,6 @@ impl Status {
/// Refresh the current view, reloading the files. Move the selection to top.
pub fn refresh_view(&mut self) -> Result<()> {
- self.menu.encrypted_devices.update()?;
self.refresh_status()?;
self.update_second_pane_for_preview()
}
@@ -277,11 +292,14 @@ impl Status {
Ok(())
}
- fn reset_edit_mode(&mut self) {
- self.menu.completion.reset();
- self.current_tab_mut().reset_edit_mode();
+ /// Reset the edit mode to "Nothing" (closing any menu) and returns
+ /// true if the display should be refreshed.
+ pub fn reset_edit_mode(&mut self) -> Result<bool> {
+ self.menu.reset();
+ let must_refresh = matches!(self.current_tab().display_mode, Display::Preview);
+ self.set_edit_mode(self.index, Edit::Nothing)?;
+ Ok(must_refresh)
}
- /// Refresh the existing users.
/// Reset the selected tab view to the default.
pub fn refresh_status(&mut self) -> Result<()> {
@@ -298,6 +316,7 @@ impl Status {
self.internal_settings.force_clear();
}
+ /// Refresh the users for every tab
pub fn refresh_users(&mut self) -> Result<()> {
let users = Users::new();
self.tabs[0].users = users.clone();
@@ -305,6 +324,7 @@ impl Status {
Ok(())
}
+ /// Refresh the input, completion and every tab.
pub fn refresh_tabs(&mut self) -> Result<()> {
self.menu.input.reset();
self.menu.completion.reset();
@@ -332,31 +352,60 @@ impl Status {
/// Check if the second pane should display a preview and force it.
pub fn update_second_pane_for_preview(&mut self) -> Result<()> {
if self.index == 0 && self.display_settings.preview() {
- self.set_second_pane_for_preview()?;
+ if Session::display_wide_enough(self.term_size()?.0) {
+ self.set_second_pane_for_preview()?;
+ } else {
+ self.tabs[1].preview = Preview::empty();
+ }
};
Ok(())
}
/// Force preview the selected file of the first pane in the second pane.
/// Doesn't check if it has do.
- pub fn set_second_pane_for_preview(&mut self) -> Result<()> {
- if !Session::display_wide_enough(self.term_size()?.0) {
- self.tabs[1].preview = Preview::empty();
+ fn set_second_pane_for_preview(&mut self) -> Result<()> {
+ self.tabs[1].set_display_mode(Display::Preview);
+ self.tabs[1].edit_mode = Edit::Nothing;
+ let users = &self.tabs[0].users;
+ let fileinfo = match self.tabs[0].display_mode {
+ Display::Flagged => {
+ let Some(path) = self.menu.flagged.selected() else {
+ self.tabs[1].preview = Preview::empty();
+ return Ok(());
+ };
+ FileInfo::new(path, users)?
+ }
+ _ => self.tabs[0].current_file()?,
+ };
+ self.tabs[1].preview = Preview::new(&fileinfo, users).unwrap_or_default();
+ self.tabs[1].window.reset(self.tabs[1].preview.len());
+ Ok(())
+ }
+
+ /// Set an edit mode for the tab at `index`. Refresh the view.
+ pub fn set_edit_mode(&mut self, index: usize, edit_mode: Edit) -> Result<()> {
+ if index > 1 {
return Ok(());
}
+ self.set_height_for_edit_mode(index, edit_mode)?;
+ self.tabs[index].edit_mode = edit_mode;
+ let len = self.menu.len(edit_mode);
+ let height = self.second_window_height()?;
+ self.menu.window = ContentWindow::new(len, height);
+ self.refresh_status()
+ }
- self.tabs[1].set_display_mode(Display::Preview);
- self.tabs[1].set_edit_mode(Edit::Nothing);
- let fileinfo = self.tabs[0]
- .current_file()
- .context("force preview: No file to select")?;
- let preview = match fileinfo.file_kind {
- FileKind::Directory => Preview::directory(&fileinfo, &self.tabs[0].users),
- _ => Preview::file(&fileinfo),
+ pub fn set_height_for_edit_mode(&mut self, index: usize, edit_mode: Edit) -> Result<()> {
+ let height = self.internal_settings.term.term_size()?.1;
+ let prim_window_height = if matches!(edit_mode, Edit::Nothing) {
+ height
+ } else {
+ height / 2
};
- self.tabs[1].preview = preview.unwrap_or_default();
-
- self.tabs[1].window.reset(self.tabs[1].preview.len());
+ self.tabs[index].window.set_height(prim_window_height);
+ self.tabs[index]
+ .window
+ .scroll_to(self.tabs[index].window.top);
Ok(())
}
@@ -383,44 +432,67 @@ impl Status {
/// directory before calling Bulkrename.
/// It may be confusing since the same filename can be used in
/// different places.
- pub fn flagged_in_current_dir(&self) -> Vec<&Path> {
- self.menu
- .flagged
- .in_current_dir(&self.current_tab().directory.path)
+ pub fn flagged_in_current_dir(&self) -> Vec<std::path::PathBuf> {
+ self.menu.flagged.in_dir(&self.current_tab().directory.path)
}
- /// Flag all files in the current directory.
+ /// Flag all files in the current directory or current tree.
pub fn flag_all(&mut self) {
- self.tabs[self.index]
- .directory
- .content
- .iter()
- .for_each(|file| {
- self.menu.flagged.push(file.path.to_path_buf());
- });
+ match self.current_tab().display_mode {
+ Display::Directory => {
+ self.tabs[self.index]
+ .directory
+ .content
+ .iter()
+ .for_each(|file| {
+ self.menu.flagged.push(file.path.to_path_buf());
+ });
+ }
+ Display::Tree => self.tabs[self.index].tree.flag_all(&mut self.menu.flagged),
+ _ => (),
+ }
}
/// Reverse every flag in _current_ directory. Flagged files in other
/// directory aren't affected.
pub fn reverse_flags(&mut self) {
- self.tabs[self.index]
- .directory
- .content
- .iter()
- .for_each(|file| self.menu.flagged.toggle(&file.path));
+ match self.current_tab().display_mode {
+ Display::Preview => (),
+ Display::Tree => (),
+ Display::Flagged => (),
+ Display::Directory => {
+ self.tabs[self.index]
+ .directory
+ .content
+ .iter()
+ .for_each(|file| self.menu.flagged.toggle(&file.path));
+ }
+ }
}
/// Flag the selected file if any
pub fn toggle_flag_for_selected(&mut self) {
let tab = self.current_tab();
- if matches!(tab.edit_mode, Edit::Nothing) && !matches!(tab.display_mode, Display::Preview) {
+ if matches!(tab.edit_mode, Edit::Nothing) {
let Ok(file) = tab.current_file() else {
return;
};
- self.menu.flagged.toggle(&file.path);
- self.current_tab_mut().normal_down_one_row();
- };
+ match tab.display_mode {
+ Display::Flagged => {
+ self.menu.flagged.remove_selected();
+ }
+ Display::Directory => {
+ self.menu.flagged.toggle(&file.path);
+ self.current_tab_mut().normal_down_one_row();
+ }
+ Display::Tree => {
+ self.menu.flagged.toggle(&file.path);
+ let _ = self.current_tab_mut().tree_select_next();
+ }
+ Display::Preview => (),
+ }
+ }
}
/// Execute a move or a copy of the flagged files to current directory.
@@ -457,6 +529,11 @@ impl Status {
return Ok(());
};
let skim = skimer.search_filename(&self.current_tab().directory_str());
+ let paths: Vec<std::path::PathBuf> = skim
+ .iter()
+ .map(|s| std::path::PathBuf::from(s.output().to_string()))
+ .collect();
+ self.menu.flagged.update(paths);
let Some(output) = skim.first() else {
return Ok(());
};
@@ -477,6 +554,11 @@ impl Status {
return Ok(());
};
let skim = skimer.search_line_in_file(&self.current_tab().directory_str());
+ let paths: Vec<std::path::PathBuf> = skim
+ .iter()
+ .map(|s| std::path::PathBuf::from(s.output().to_string()))
+ .collect();
+ self.menu.flagged.update(paths);
let Some(output) = skim.first() else {
return Ok(());
};
@@ -549,13 +631,7 @@ impl Status {
Ok(())
}
- pub fn execute_bulk(&self) -> Result<()> {
- if let Some(bulk) = &self.menu.bulk {
- bulk.execute_bulk(self)?;
- }
- Ok(())
- }
-
+ /// Update the flagged files depending of the input regex.
pub fn input_regex(&mut self, char: char) -> Result<()> {
self.menu.input.insert(char);
self.select_from_regex()?;
@@ -571,7 +647,7 @@ impl Status {
let paths = match self.current_tab().display_mode {
Display::Directory => self.tabs[self.index].directory.paths(),
Display::Tree => self.tabs[self.index].tree.paths(),
- Display::Preview => return Ok(()),
+ _ => return Ok(()),
};
regex_matcher(&input, &paths, &mut self.menu.flagged)?;
Ok(())
@@ -701,6 +777,7 @@ impl Status {
}
}
+ /// Move to the selected removable device.
pub fn go_to_removable(&mut self) -> Result<()> {
let Some(path) = self.menu.find_removable_mount_point() else {
return Ok(());
@@ -709,12 +786,14 @@ impl Status {
self.current_tab_mut().refresh_view()
}
+ /// Reads and parse a shell command. Some arguments may be expanded.
+ /// See [`crate::modes::edit::ShellCommandParser`] for more information.
pub fn parse_shell_command(&mut self) -> Result<bool> {
let shell_command = self.menu.input.string();
let mut args = ShellCommandParser::new(&shell_command).compute(self)?;
log_info!("command {shell_command} args: {args:?}");
if args_is_empty(&args) {
- self.current_tab_mut().set_edit_mode(Edit::Nothing);
+ self.set_edit_mode(self.index, Edit::Nothing)?;
return Ok(true);
}
let executable = args.remove(0);
@@ -733,7 +812,6 @@ impl Status {
{
self.preview_command_output(output, shell_command);
}
- // self.current_tab_mut().set_edit_mode(Edit::Nothing);
Ok(true)
}
}
@@ -745,43 +823,28 @@ impl Status {
password_dest: PasswordUsage,
) -> Result<()> {
log_info!("event ask password");
- self.current_tab_mut()
- .set_edit_mode(Edit::InputSimple(InputSimple::Password(
- encrypted_action,
- password_dest,
- )));
- Ok(())
- }
-
- pub fn execute_password_command(&mut self) -> Result<()> {
- match self.current_tab().edit_mode {
- Edit::InputSimple(InputSimple::Password(action, dest)) => {
- self._execute_password_command(action, dest)?;
- }
- _ => {
- return Err(anyhow!(
- "execute_password_command: edit_mode should be `InputSimple::Password`"
- ))
- }
- }
- Ok(())
+ self.set_edit_mode(
+ self.index,
+ Edit::InputSimple(InputSimple::Password(encrypted_action, password_dest)),
+ )
}
- fn _execute_password_command(
+ /// Attach the typed password to the correct receiver and
+ /// execute the command requiring a password.
+ pub fn execute_password_command(
&mut self,
action: Option<BlockDeviceAction>,
dest: PasswordUsage,
) -> Result<()> {
let password = self.menu.input.string();
self.menu.input.reset();
- match dest {
- PasswordUsage::CRYPTSETUP(PasswordKind::CRYPTSETUP) => {
- self.menu.password_holder.set_cryptsetup(password)
- }
- _ => self.menu.password_holder.set_sudo(password),
+ if matches!(dest, PasswordUsage::CRYPTSETUP(PasswordKind::CRYPTSETUP)) {
+ self.menu.password_holder.set_cryptsetup(password)
+ } else {
+ self.menu.password_holder.set_sudo(password)
};
- self.reset_edit_mode();
- self.dispatch_password(dest, action)
+ self.reset_edit_mode()?;
+ self.dispatch_password(action, dest)
}
/// Execute a new mark, saving it to a config file for futher use.
@@ -792,7 +855,7 @@ impl Status {
let tab: &mut Tab = self.current_tab_mut();
tab.refresh_view()
}?;
- self.reset_edit_mode();
+ self.reset_edit_mode()?;
self.refresh_status()
}
@@ -803,14 +866,14 @@ impl Status {
self.current_tab_mut().cd(&path)?;
}
self.current_tab_mut().refresh_view()?;
- self.reset_edit_mode();
+ self.reset_edit_mode()?;
self.refresh_status()
}
/// Recursively delete all flagged files.
pub fn confirm_delete_files(&mut self) -> Result<()> {
self.menu.delete_flagged_files()?;
- self.reset_edit_mode();
+ self.reset_edit_mode()?;
self.clear_flags_and_reset_view()?;
self.refresh_status()
}
@@ -818,13 +881,50 @@ impl Status {
/// Empty the trash folder permanently.
pub fn confirm_trash_empty(&mut self) -> Result<()> {
self.menu.trash.empty_trash()?;
- self.reset_edit_mode();
+ self.reset_edit_mode()?;
self.clear_flags_and_reset_view()?;
Ok(())
}
+ /// Ask the new filenames and set the confirmation mode.
+ pub fn bulk_ask_filenames(&mut self) -> Result<()> {
+ self.bulk_flag_selection_for_rename()?;
+ let flagged = self.flagged_in_current_dir();
+ let current_path = self.current_tab_path_str();
+ let bulk_action =
+ self.menu
+ .bulk
+ .ask_filenames(flagged, &current_path, &self.internal_settings.opener)?;
+ self.set_edit_mode(
+ self.index,
+ Edit::NeedConfirmation(NeedConfirmation::BulkAction(bulk_action)),
+ )?;
+ Ok(())
+ }
+
+ fn bulk_flag_selection_for_rename(&mut self) -> Result<()> {
+ if self.menu.flagged.is_empty() && self.menu.bulk.is_rename() {
+ self.menu
+ .flagged
+ .push(self.current_tab().current_file()?.path.to_path_buf());
+ }
+ Ok(())
+ }
+
+ /// Execute the bulk action.
+ pub fn confirm_bulk_action(&mut self) -> Result<()> {
+ if let Some(paths) = self.menu.bulk.execute()? {
+ self.menu.flagged.update(paths);
+ } else {
+ self.menu.flagged.clear();
+ };
+ self.reset_edit_mode()?;
+ self.reset_tabs_view()?;
+ Ok(())
+ }
+
fn run_sudo_command(&mut self) -> Result<()> {
- self.current_tab_mut().set_edit_mode(Edit::Nothing);
+ self.set_edit_mode(self.index, Edit::Nothing)?;
reset_sudo_faillock()?;
let Some(sudo_command) = self.menu.sudo_command.to_owned() else {
return self.menu.clear_sudo_attributes();
@@ -848,10 +948,12 @@ impl Status {
Ok(())
}
+ /// Dispatch the known password depending of which component set
+ /// the `PasswordUsage`.
pub fn dispatch_password(
&mut self,
- dest: PasswordUsage,
action: Option<BlockDeviceAction>,
+ dest: PasswordUsage,
) -> Result<()> {
match dest {
PasswordUsage::ISO => match action {
@@ -868,15 +970,17 @@ impl Status {
}
}
+ /// Set the display to preview a command output
pub fn preview_command_output(&mut self, output: String, command: String) {
log_info!("output {output}");
- self.current_tab_mut().reset_edit_mode();
+ let _ = self.reset_edit_mode();
self.current_tab_mut().set_display_mode(Display::Preview);
let preview = Preview::cli_info(&output, command);
self.current_tab_mut().window.reset(preview.len());
self.current_tab_mut().preview = preview;
}
+ /// Set the nvim listen address from what the user typed.
pub fn update_nvim_listen_address(&mut self) {
self.internal_settings.update_nvim_listen_address()
}
@@ -887,31 +991,38 @@ impl Status {
if c == 'y' {
let _ = self.match_confirmed_mode(confirmed_action);
}
- self.reset_edit_mode();
+ self.reset_edit_mode()?;
self.current_tab_mut().refresh_view()?;
Ok(())
}
+ /// Execute a `NeedConfirmation` action (delete, move, copy, empty trash)
fn match_confirmed_mode(&mut self, confirmed_action: NeedConfirmation) -> Result<()> {
match confirmed_action {
NeedConfirmation::Delete => self.confirm_delete_files(),
NeedConfirmation::Move => self.cut_or_copy_flagged_files(CopyMove::Move),
NeedConfirmation::Copy => self.cut_or_copy_flagged_files(CopyMove::Copy),
NeedConfirmation::EmptyTrash => self.confirm_trash_empty(),
+ NeedConfirmation::BulkAction(_) => self.confirm_bulk_action(),
}
}
+ /// Execute an action when the header line was clicked.
pub fn header_action(&mut self, col: u16, binds: &Bindings) -> Result<()> {
- if matches!(self.current_tab().display_mode, Display::Preview) {
- return Ok(());
- }
let is_right = self.index == 1;
- let header = Header::new(self, self.current_tab())?;
- let action = header.action(col as usize, is_right);
- action.matcher(self, binds)
+ match self.current_tab().display_mode {
+ Display::Preview => Ok(()),
+ Display::Flagged => FlaggedHeader::new(self)?
+ .action(col as usize, is_right)
+ .matcher(self, binds),
+ _ => Header::new(self, self.current_tab())?
+ .action(col as usize, is_right)
+ .matcher(self, binds),
+ }
}
+ /// Execute an action when the footer line was clicked.
pub fn footer_action(&mut self, col: u16, binds: &Bindings) -> Result<()> {
log_info!("footer clicked col {col}");
if matches!(self.current_tab().display_mode, Display::Preview) {
@@ -937,6 +1048,7 @@ impl Status {
self.reset_tabs_view()
}
+ /// Enter the chmod mode where user can chmod a file.
pub fn set_mode_chmod(&mut self) -> Result<()> {
if self.current_tab_mut().directory.is_empty() {
return Ok(());
@@ -944,9 +1056,7 @@ impl Status {
if self.menu.flagged.is_empty() {
self.toggle_flag_for_selected();
}
- self.current_tab_mut()
- .set_edit_mode(Edit::InputSimple(InputSimple::Chmod));
- Ok(())
+ self.set_edit_mode(self.index, Edit::InputSimple(InputSimple::Chmod))
}
/// Add a char to input string, look for a possible completion.
@@ -965,6 +1075,22 @@ impl Status {
log_info!("output {output}");
Ok(())
}
+
+ pub fn fuzzy_flags(&mut self) -> Result<()> {
+ self.current_tab_mut().set_display_mode(Display::Flagged);
+ Ok(())
+ }
+
+ pub fn sort(&mut self, c: char) -> Result<()> {
+ self.current_tab_mut().sort(c)?;
+ self.menu.reset();
+ self.set_height_for_edit_mode(self.index, Edit::Nothing)?;
+ self.tabs[self.index].edit_mode = Edit::Nothing;
+ let len = self.menu.len(Edit::Nothing);
+ let height = self.second_window_height()?;
+ self.menu.window = ContentWindow::new(len, height);
+ Ok(())
+ }
}
fn parse_keyname(keyname: &str) -> Option<String> {