mirror of
https://github.com/Dummi26/tuifile.git
synced 2025-12-18 01:57:50 +01:00
initial commit
This commit is contained in:
392
src/main.rs
Executable file
392
src/main.rs
Executable file
@@ -0,0 +1,392 @@
|
||||
mod run;
|
||||
mod tasks;
|
||||
mod updates;
|
||||
|
||||
use std::{
|
||||
fs::{self, DirEntry, Metadata},
|
||||
io::{self, StdoutLock},
|
||||
path::PathBuf,
|
||||
rc::Rc,
|
||||
sync::{Arc, Mutex},
|
||||
thread::JoinHandle,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use clap::{command, Parser};
|
||||
use crossterm::terminal;
|
||||
use regex::Regex;
|
||||
use updates::Updates;
|
||||
|
||||
const EXIT_NO_ABSOLUTE_PATH: i32 = 1;
|
||||
|
||||
fn main() -> io::Result<()> {
|
||||
let args = Args::parse();
|
||||
let current_dir = match args.dir {
|
||||
Some(dir) => {
|
||||
if args.dir_relative || dir.is_absolute() {
|
||||
dir
|
||||
} else {
|
||||
match fs::canonicalize(dir) {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
eprintln!("Error getting absolute path: {e}.");
|
||||
std::process::exit(EXIT_NO_ABSOLUTE_PATH);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None => std::env::current_dir().unwrap_or(PathBuf::from("/")),
|
||||
};
|
||||
let mut share = Share {
|
||||
status: String::new(),
|
||||
tasks: vec![],
|
||||
active_instance: 0,
|
||||
total_instances: 1,
|
||||
stdout: io::stdout().lock(),
|
||||
size: terminal::size()?,
|
||||
terminal_command: std::env::var("TERM").unwrap_or("alacritty".to_string()),
|
||||
editor_command: std::env::var("EDITOR").unwrap_or("nano".to_string()),
|
||||
live_search: !args.no_live_search,
|
||||
};
|
||||
if args.check {
|
||||
eprintln!("Terminal: {}", share.terminal_command);
|
||||
eprintln!("Editor: {}", share.editor_command);
|
||||
return Ok(());
|
||||
}
|
||||
let mut instances = vec![TuiFile::new(current_dir)?];
|
||||
TuiFile::term_setup_no_redraw(&mut share)?;
|
||||
let mut redraw = true;
|
||||
loop {
|
||||
if instances.is_empty() {
|
||||
break;
|
||||
}
|
||||
if share.active_instance >= instances.len() {
|
||||
share.active_instance = instances.len() - 1;
|
||||
}
|
||||
share.total_instances = instances.len();
|
||||
let instance = &mut instances[share.active_instance];
|
||||
if redraw {
|
||||
instance.updates.request_clear();
|
||||
instance.updates.request_redraw();
|
||||
if instance.active {
|
||||
share.status = format!("{}", share.active_instance);
|
||||
}
|
||||
}
|
||||
let cmd = instance.run(&mut share)?;
|
||||
redraw = match cmd {
|
||||
AppCmd::Quit => break,
|
||||
AppCmd::CloseInstance => {
|
||||
instances.remove(share.active_instance);
|
||||
if share.active_instance > 0 {
|
||||
share.active_instance -= 1;
|
||||
}
|
||||
true
|
||||
}
|
||||
AppCmd::NextInstance => {
|
||||
if share.active_instance + 1 < instances.len() {
|
||||
share.active_instance += 1;
|
||||
}
|
||||
true
|
||||
}
|
||||
AppCmd::PrevInstance => {
|
||||
if share.active_instance > 0 {
|
||||
share.active_instance -= 1;
|
||||
}
|
||||
true
|
||||
}
|
||||
AppCmd::AddInstance(new) => {
|
||||
share.active_instance += 1;
|
||||
instances.insert(share.active_instance, new);
|
||||
true
|
||||
}
|
||||
AppCmd::CopyTo(destination) => {
|
||||
instance.updates.request_redraw_infobar();
|
||||
let src = instances
|
||||
.iter()
|
||||
.filter(|v| v.active)
|
||||
.map(|v| {
|
||||
(
|
||||
v.current_dir.clone(),
|
||||
v.dir_content
|
||||
.iter()
|
||||
.filter(|e| e.selected)
|
||||
.filter_map(|e| {
|
||||
Some((
|
||||
e.entry
|
||||
.path()
|
||||
.strip_prefix(&v.current_dir)
|
||||
.ok()?
|
||||
.to_owned(),
|
||||
e.rel_depth == v.scan_files_max_depth,
|
||||
))
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
tasks::task_copy(src, destination, &mut share);
|
||||
false
|
||||
}
|
||||
AppCmd::TaskFinished => {
|
||||
for i in &mut instances {
|
||||
i.updates.request_rescan_files();
|
||||
}
|
||||
false
|
||||
}
|
||||
};
|
||||
}
|
||||
TuiFile::term_reset(&mut share)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// TUI file explorer. Long Help is available with --help.
|
||||
///
|
||||
/// Controls:
|
||||
/// - Ctrl+Up/K => previous
|
||||
/// - Ctrl+Down/J => next
|
||||
/// - Ctrl+Left/H => close
|
||||
/// - Ctrl+Right/L => duplicate
|
||||
/// Files:
|
||||
/// - Up/K or Down/J => move selection
|
||||
/// - Left/H => go to parent directory
|
||||
/// - Right/L => go into selected entry
|
||||
/// - A => Alternate selection (toggle All)
|
||||
/// - S => Select or toggle current
|
||||
/// - D => Deselect all
|
||||
/// - F => focus Find/Filter bar
|
||||
/// - N => New directory from search text
|
||||
/// - C => Copy selected files to this directory.
|
||||
/// - 1-9 or 0 => set recursive depth limit (0 = infinite)
|
||||
/// - T => open terminal here ($TERM)
|
||||
/// - E => open in editor ($EDITOR <file/dir>)
|
||||
/// Find/Filter Bar:
|
||||
/// - Esc: back and discard
|
||||
/// - Enter: back and apply
|
||||
/// - Backspace: delete
|
||||
/// - type to enter search regex
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, verbatim_doc_comment)]
|
||||
struct Args {
|
||||
/// the directory you want to view.
|
||||
dir: Option<PathBuf>,
|
||||
/// skips converting the 'dir' argument to an absolute path.
|
||||
/// this causes issues when trying to view parent directories
|
||||
/// but may be necessary if tuifile doesn't start.
|
||||
#[arg(long)]
|
||||
dir_relative: bool,
|
||||
/// performs some checks and prints results.
|
||||
#[arg(long)]
|
||||
check: bool,
|
||||
/// disables live search, only filtering the file list when enter is pressed.
|
||||
#[arg(long)]
|
||||
no_live_search: bool,
|
||||
}
|
||||
|
||||
struct Share {
|
||||
status: String,
|
||||
tasks: Vec<BackgroundTask>,
|
||||
active_instance: usize,
|
||||
total_instances: usize,
|
||||
size: (u16, u16),
|
||||
stdout: StdoutLock<'static>,
|
||||
//
|
||||
live_search: bool,
|
||||
terminal_command: String,
|
||||
editor_command: String,
|
||||
}
|
||||
impl Share {
|
||||
fn check_bgtasks(&mut self) -> bool {
|
||||
for (i, task) in self.tasks.iter_mut().enumerate() {
|
||||
if task.thread.is_finished() {
|
||||
self.tasks.remove(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
struct BackgroundTask {
|
||||
status: Arc<Mutex<String>>,
|
||||
thread: JoinHandle<Result<(), String>>,
|
||||
}
|
||||
impl BackgroundTask {
|
||||
pub fn new(
|
||||
func: impl FnOnce(Arc<Mutex<String>>) -> Result<(), String> + Send + 'static,
|
||||
) -> Self {
|
||||
let status = Arc::new(Mutex::new(String::new()));
|
||||
Self {
|
||||
status: Arc::clone(&status),
|
||||
thread: std::thread::spawn(move || func(status)),
|
||||
}
|
||||
}
|
||||
}
|
||||
struct TuiFile {
|
||||
active: bool,
|
||||
updates: u32,
|
||||
current_dir: PathBuf,
|
||||
dir_content: Vec<DirContent>,
|
||||
dir_content_len: usize,
|
||||
scroll: usize,
|
||||
current_index: usize,
|
||||
focus: Focus,
|
||||
scan_files_max_depth: usize,
|
||||
files_status_is_special: bool,
|
||||
files_status: String,
|
||||
search_text: String,
|
||||
search_regex: Option<Regex>,
|
||||
last_drawn_files_height: usize,
|
||||
last_drawn_files_count: usize,
|
||||
last_files_max_scroll: usize,
|
||||
after_rescanning_files: Vec<Box<dyn FnOnce(&mut Self)>>,
|
||||
}
|
||||
#[derive(Clone)]
|
||||
struct DirContent {
|
||||
entry: Rc<DirEntry>,
|
||||
name: String,
|
||||
name_charlen: usize,
|
||||
rel_depth: usize,
|
||||
passes_filter: bool,
|
||||
selected: bool,
|
||||
more: DirContentType,
|
||||
}
|
||||
#[derive(Clone)]
|
||||
enum DirContentType {
|
||||
/// Couldn't get more info on this entry
|
||||
Err(String),
|
||||
Dir {
|
||||
metadata: Metadata,
|
||||
},
|
||||
File {
|
||||
size: String,
|
||||
metadata: Metadata,
|
||||
},
|
||||
Symlink {
|
||||
metadata: Metadata,
|
||||
},
|
||||
}
|
||||
#[derive(Clone)]
|
||||
enum Focus {
|
||||
Files,
|
||||
SearchBar,
|
||||
}
|
||||
enum AppCmd {
|
||||
Quit,
|
||||
CloseInstance,
|
||||
NextInstance,
|
||||
PrevInstance,
|
||||
AddInstance(TuiFile),
|
||||
CopyTo(PathBuf),
|
||||
TaskFinished,
|
||||
}
|
||||
impl TuiFile {
|
||||
pub fn clone(&self) -> Self {
|
||||
Self {
|
||||
active: self.active,
|
||||
updates: 0,
|
||||
current_dir: self.current_dir.clone(),
|
||||
dir_content: self.dir_content.clone(),
|
||||
dir_content_len: self.dir_content_len,
|
||||
scroll: self.scroll,
|
||||
current_index: self.current_index,
|
||||
focus: self.focus.clone(),
|
||||
scan_files_max_depth: self.scan_files_max_depth,
|
||||
files_status_is_special: self.files_status_is_special,
|
||||
files_status: self.files_status.clone(),
|
||||
search_text: self.search_text.clone(),
|
||||
search_regex: self.search_regex.clone(),
|
||||
last_drawn_files_height: self.last_drawn_files_height,
|
||||
last_drawn_files_count: self.last_drawn_files_count,
|
||||
last_files_max_scroll: self.last_files_max_scroll,
|
||||
after_rescanning_files: vec![],
|
||||
}
|
||||
}
|
||||
pub fn new(current_dir: PathBuf) -> io::Result<Self> {
|
||||
// state
|
||||
let (width, height) = terminal::size()?;
|
||||
let updates = u32::MAX;
|
||||
Ok(Self {
|
||||
active: true,
|
||||
updates,
|
||||
current_dir,
|
||||
dir_content: vec![],
|
||||
dir_content_len: 0,
|
||||
scroll: 0,
|
||||
current_index: 0,
|
||||
focus: Focus::Files,
|
||||
scan_files_max_depth: 0,
|
||||
files_status_is_special: false,
|
||||
files_status: String::new(),
|
||||
search_text: String::new(),
|
||||
search_regex: None,
|
||||
last_drawn_files_height: 0,
|
||||
last_drawn_files_count: 0,
|
||||
last_files_max_scroll: 0,
|
||||
after_rescanning_files: vec![],
|
||||
})
|
||||
}
|
||||
fn set_current_index(&mut self, mut i: usize) {
|
||||
if i >= self.dir_content.len() {
|
||||
i = self.dir_content.len().saturating_sub(1);
|
||||
}
|
||||
if i == self.current_index {
|
||||
return;
|
||||
}
|
||||
if i < self.scroll {
|
||||
self.scroll = i;
|
||||
self.updates.request_redraw_filelist();
|
||||
}
|
||||
if i >= self.scroll + self.last_drawn_files_height {
|
||||
self.scroll = 1 + i - self.last_drawn_files_height;
|
||||
self.updates.request_redraw_filelist();
|
||||
}
|
||||
self.updates.request_move_cursor();
|
||||
// self.updates.request_redraw_filelist();
|
||||
self.current_index = i;
|
||||
}
|
||||
/// starting from `start`, checks all indices until it finds a visible entry or there are no more entries.
|
||||
/// If an entry was found, the current_index will be set to that entry.
|
||||
fn set_current_index_to_visible(&mut self, start: usize, inc: bool) {
|
||||
let mut i = start;
|
||||
loop {
|
||||
if self.dir_content.get(i).is_some_and(|e| e.passes_filter) {
|
||||
self.set_current_index(i);
|
||||
return;
|
||||
}
|
||||
if inc {
|
||||
i += 1;
|
||||
if i >= self.dir_content.len() {
|
||||
break;
|
||||
}
|
||||
} else if i > 0 {
|
||||
i -= 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
fn request_rescan_files_then_select(
|
||||
&mut self,
|
||||
find_by: impl FnMut(&DirContent) -> bool + 'static,
|
||||
) {
|
||||
self.updates.request_rescan_files();
|
||||
self.after_rescanning_files.push(Box::new(move |s| {
|
||||
if let Some(i) = s.dir_content.iter().position(find_by) {
|
||||
s.set_current_index(i)
|
||||
} else {
|
||||
s.updates.request_reset_current_index();
|
||||
}
|
||||
}));
|
||||
}
|
||||
fn request_rescan_files_then_select_by_name(&mut self, name: String) {
|
||||
self.request_rescan_files_then_select(move |e| {
|
||||
e.name == name || e.name.ends_with('/') && e.name[..e.name.len() - 1] == name
|
||||
});
|
||||
}
|
||||
fn request_rescan_files_then_select_current_again(&mut self) {
|
||||
if let Some(c) = self.dir_content.get(self.current_index) {
|
||||
self.request_rescan_files_then_select_by_name(c.name.clone());
|
||||
} else {
|
||||
self.updates.request_rescan_files();
|
||||
}
|
||||
}
|
||||
}
|
||||
701
src/run.rs
Executable file
701
src/run.rs
Executable file
@@ -0,0 +1,701 @@
|
||||
use crossterm::event::{poll, read, Event, KeyCode, KeyModifiers};
|
||||
use crossterm::style::{Attribute, Color, Stylize};
|
||||
use crossterm::{cursor, queue, style, terminal, ExecutableCommand};
|
||||
use regex::RegexBuilder;
|
||||
|
||||
use crate::updates::Updates;
|
||||
use crate::{tasks, AppCmd, BackgroundTask, DirContent, DirContentType, Focus, Share};
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::process::{Command, Stdio};
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
use std::{fs, io};
|
||||
|
||||
use crate::TuiFile;
|
||||
|
||||
const BYTE_UNITS: [&'static str; 6] = ["B", "KB", "MB", "GB", "TB", "PB"];
|
||||
|
||||
impl TuiFile {
|
||||
pub fn term_setup(&mut self, share: &mut Share) -> io::Result<()> {
|
||||
self.updates.request_redraw();
|
||||
Self::term_setup_no_redraw(share)
|
||||
}
|
||||
pub fn term_setup_no_redraw(share: &mut Share) -> io::Result<()> {
|
||||
share.stdout.execute(terminal::EnterAlternateScreen)?;
|
||||
terminal::enable_raw_mode()?;
|
||||
Ok(())
|
||||
}
|
||||
pub fn term_reset(share: &mut Share) -> io::Result<()> {
|
||||
terminal::disable_raw_mode()?;
|
||||
share.stdout.execute(terminal::LeaveAlternateScreen)?;
|
||||
Ok(())
|
||||
}
|
||||
pub fn run(&mut self, share: &mut Share) -> io::Result<AppCmd> {
|
||||
loop {
|
||||
if share.check_bgtasks() {
|
||||
return Ok(AppCmd::TaskFinished);
|
||||
}
|
||||
// rescan files if necessary
|
||||
if self.updates.rescan_files() {
|
||||
self.updates.dont_rescan_files();
|
||||
self.updates.request_filter_files();
|
||||
self.files_status_is_special = false;
|
||||
self.dir_content.clear();
|
||||
get_files(self, self.current_dir.clone(), 0);
|
||||
fn get_files(s: &mut TuiFile, dir: PathBuf, depth: usize) {
|
||||
match fs::read_dir(&dir) {
|
||||
Err(e) => {
|
||||
if depth == 0 {
|
||||
s.dir_content = vec![];
|
||||
s.files_status = format!("{e}");
|
||||
s.files_status_is_special = true;
|
||||
}
|
||||
}
|
||||
Ok(files) => {
|
||||
for entry in files {
|
||||
if let Ok(entry) = entry {
|
||||
let mut name = entry.file_name().to_string_lossy().into_owned();
|
||||
let metadata = entry.metadata();
|
||||
let p = entry.path();
|
||||
let more = match metadata {
|
||||
Err(e) => DirContentType::Err(e.to_string()),
|
||||
Ok(metadata) => {
|
||||
if metadata.is_symlink() {
|
||||
DirContentType::Symlink { metadata }
|
||||
} else if metadata.is_file() {
|
||||
DirContentType::File {
|
||||
size: {
|
||||
let mut bytes = metadata.len();
|
||||
let mut i = 0;
|
||||
loop {
|
||||
if bytes < 1024
|
||||
|| i + 1 >= BYTE_UNITS.len()
|
||||
{
|
||||
break format!(
|
||||
"{bytes}{}",
|
||||
BYTE_UNITS[i]
|
||||
);
|
||||
} else {
|
||||
i += 1;
|
||||
// divide by 1024 but cooler
|
||||
bytes >>= 10;
|
||||
}
|
||||
}
|
||||
},
|
||||
metadata,
|
||||
}
|
||||
} else if metadata.is_dir() {
|
||||
DirContentType::Dir { metadata }
|
||||
} else {
|
||||
DirContentType::Err(format!(
|
||||
"not a file, dir or symlink"
|
||||
))
|
||||
}
|
||||
}
|
||||
};
|
||||
if let DirContentType::Dir { .. } = more {
|
||||
name.push('/');
|
||||
}
|
||||
s.dir_content.push(DirContent {
|
||||
entry: Rc::new(entry),
|
||||
name_charlen: name.chars().count(),
|
||||
name,
|
||||
rel_depth: depth,
|
||||
passes_filter: true,
|
||||
selected: false,
|
||||
more,
|
||||
});
|
||||
if depth < s.scan_files_max_depth {
|
||||
get_files(s, p, depth + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if self.current_index >= self.dir_content.len() {
|
||||
self.current_index = self.dir_content.len().saturating_sub(1);
|
||||
}
|
||||
if !self.after_rescanning_files.is_empty() {
|
||||
for func in std::mem::replace(&mut self.after_rescanning_files, vec![]) {
|
||||
func(self);
|
||||
}
|
||||
}
|
||||
}
|
||||
if self.updates.reset_search() {
|
||||
self.updates.dont_reset_search();
|
||||
if !self.search_text.is_empty() {
|
||||
self.search_text.clear();
|
||||
self.search_regex = None;
|
||||
self.updates.request_redraw_searchbar();
|
||||
}
|
||||
}
|
||||
if self.updates.filter_files() {
|
||||
if self.search_regex.is_none() && !self.search_text.is_empty() {
|
||||
self.search_regex = RegexBuilder::new(&self.search_text)
|
||||
.case_insensitive(true)
|
||||
.build()
|
||||
.ok();
|
||||
}
|
||||
self.updates.dont_filter_files();
|
||||
self.updates.request_redraw_filelist();
|
||||
if let Some(regex) = &self.search_regex {
|
||||
self.dir_content_len = 0;
|
||||
for entry in &mut self.dir_content {
|
||||
entry.passes_filter = regex.is_match(&entry.name);
|
||||
if entry.passes_filter {
|
||||
self.dir_content_len += 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for entry in &mut self.dir_content {
|
||||
entry.passes_filter = true;
|
||||
}
|
||||
self.dir_content_len = self.dir_content.len();
|
||||
}
|
||||
if !self.files_status_is_special {
|
||||
self.files_status = match (
|
||||
self.dir_content_len != self.dir_content.len(),
|
||||
self.dir_content.len() == 1,
|
||||
) {
|
||||
(false, false) => format!("{} entries", self.dir_content_len),
|
||||
(false, true) => format!("1 entry"),
|
||||
(true, false) => format!(
|
||||
"{} of {} entries",
|
||||
self.dir_content_len,
|
||||
self.dir_content.len()
|
||||
),
|
||||
(true, true) => format!("{} of 1 entry", self.dir_content_len),
|
||||
};
|
||||
if self.scan_files_max_depth > 0 {
|
||||
if let Some(v) = self.scan_files_max_depth.checked_add(1) {
|
||||
self.files_status.push_str(&format!(" ({v} layers)",));
|
||||
} else {
|
||||
self.files_status.push_str(&format!(" (recursive)",));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if self.updates.reset_current_index() {
|
||||
self.updates.dont_reset_current_index();
|
||||
self.set_current_index_to_visible(0, true);
|
||||
}
|
||||
// draw tui
|
||||
if share.size.0 > 0 && share.size.1 > 0 {
|
||||
if self.updates.clear() {
|
||||
self.updates.dont_clear();
|
||||
self.updates.request_move_cursor();
|
||||
queue!(share.stdout, terminal::Clear(terminal::ClearType::All))?;
|
||||
}
|
||||
if self.updates.redraw_infobar() {
|
||||
self.updates.dont_redraw_infobar();
|
||||
self.updates.request_move_cursor();
|
||||
let mut pathstring = share.status.clone();
|
||||
if share.tasks.len() > 0 {
|
||||
self.updates.request_redraw_infobar();
|
||||
for task in share.tasks.iter() {
|
||||
pathstring.push_str(" | ");
|
||||
pathstring.push_str(task.status.lock().unwrap().as_str());
|
||||
}
|
||||
}
|
||||
pathstring.push_str(" - ");
|
||||
if share.size.0 as usize > pathstring.len() {
|
||||
let mut pathchars = Vec::with_capacity(self.current_dir.as_os_str().len());
|
||||
let mut maxlen = share.size.0 as usize - pathstring.len();
|
||||
for ch in self
|
||||
.current_dir
|
||||
.as_os_str()
|
||||
.to_string_lossy()
|
||||
.to_string()
|
||||
.chars()
|
||||
.rev()
|
||||
{
|
||||
if maxlen > 0 {
|
||||
pathchars.push(ch);
|
||||
maxlen -= 1;
|
||||
}
|
||||
}
|
||||
pathstring.extend(pathchars.into_iter().rev());
|
||||
pathstring.reserve_exact(maxlen as usize);
|
||||
for _ in 0..maxlen {
|
||||
pathstring.push(' ');
|
||||
}
|
||||
queue!(
|
||||
share.stdout,
|
||||
cursor::MoveTo(0, 0),
|
||||
style::PrintStyledContent(
|
||||
pathstring
|
||||
.with(Color::Cyan)
|
||||
.attribute(Attribute::Underlined)
|
||||
)
|
||||
)?;
|
||||
}
|
||||
}
|
||||
if self.updates.redraw_filelist() {
|
||||
self.updates.dont_redraw_filelist();
|
||||
self.updates.request_move_cursor();
|
||||
self.last_drawn_files_height = share.size.1.saturating_sub(3) as _;
|
||||
let mut status = format!(" {}", self.files_status);
|
||||
while status.len() < share.size.0 as usize {
|
||||
status.push(' ');
|
||||
}
|
||||
queue!(
|
||||
share.stdout,
|
||||
cursor::MoveTo(0, 1),
|
||||
style::PrintStyledContent(status.attribute(Attribute::Italic)),
|
||||
)?;
|
||||
self.last_files_max_scroll = self
|
||||
.dir_content_len
|
||||
.saturating_sub(self.last_drawn_files_height);
|
||||
let scrollbar_where = if self.last_files_max_scroll > 0 {
|
||||
Some(
|
||||
self.last_drawn_files_height.saturating_sub(1) * self.scroll
|
||||
/ self.last_files_max_scroll,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut drawn_files = 0;
|
||||
for (line, entry) in self
|
||||
.dir_content
|
||||
.iter()
|
||||
.skip(self.scroll)
|
||||
.filter(|e| e.passes_filter)
|
||||
.take(self.last_drawn_files_height)
|
||||
.enumerate()
|
||||
{
|
||||
drawn_files += 1;
|
||||
let (mut text, mut text_charlen) = ("- ".to_string(), 2);
|
||||
for _ in 0..entry.rel_depth {
|
||||
text.push_str(" | ");
|
||||
}
|
||||
text_charlen += entry.rel_depth * 4;
|
||||
let endchar = if let Some(sb_where) = scrollbar_where {
|
||||
if line == sb_where {
|
||||
'#'
|
||||
} else {
|
||||
'|'
|
||||
}
|
||||
} else {
|
||||
' '
|
||||
};
|
||||
let styled = match &entry.more {
|
||||
DirContentType::Err(e) => {
|
||||
text.push_str(&entry.name);
|
||||
text_charlen += entry.name_charlen;
|
||||
while text_charlen + 9 > share.size.0 as usize {
|
||||
text.pop();
|
||||
text_charlen -= 1;
|
||||
}
|
||||
text.push_str(" - Err: ");
|
||||
text_charlen += 8;
|
||||
for ch in e.chars() {
|
||||
if ch == '\n' || ch == '\r' {
|
||||
continue;
|
||||
}
|
||||
if text_charlen >= share.size.0 as usize {
|
||||
break;
|
||||
}
|
||||
text_charlen += 1;
|
||||
text.push(ch);
|
||||
}
|
||||
// make text_charlen 1 too large (for the endchar)
|
||||
text_charlen += 1;
|
||||
while text_charlen < share.size.0 as _ {
|
||||
text.push(' ');
|
||||
text_charlen += 1;
|
||||
}
|
||||
text.push(endchar);
|
||||
vec![text.red()]
|
||||
}
|
||||
DirContentType::Dir { metadata } => {
|
||||
let filenamelen = share.size.0 as usize - 2 - text_charlen;
|
||||
if entry.name_charlen < filenamelen {
|
||||
text.push_str(&entry.name);
|
||||
for _ in 0..(filenamelen - entry.name_charlen) {
|
||||
text.push(' ');
|
||||
}
|
||||
} else if entry.name_charlen == filenamelen {
|
||||
text.push_str(&entry.name);
|
||||
} else {
|
||||
// the new length is the old length minus the combined length of the characters we want to cut off
|
||||
let i = entry.name.len()
|
||||
- entry
|
||||
.name
|
||||
.chars()
|
||||
.rev()
|
||||
.take(entry.name_charlen - filenamelen)
|
||||
.map(|char| char.len_utf8())
|
||||
.sum::<usize>();
|
||||
text.push_str(&entry.name[0..i.saturating_sub(3)]);
|
||||
text.push_str("...");
|
||||
}
|
||||
text.push(' ');
|
||||
text.push(endchar);
|
||||
vec![text.stylize()]
|
||||
}
|
||||
DirContentType::File { size, metadata } => {
|
||||
let filenamelen =
|
||||
share.size.0 as usize - 3 - text_charlen - size.chars().count();
|
||||
if entry.name_charlen < filenamelen {
|
||||
text.push_str(&entry.name);
|
||||
for _ in 0..(filenamelen - entry.name_charlen) {
|
||||
text.push(' ');
|
||||
}
|
||||
} else if entry.name_charlen == filenamelen {
|
||||
text.push_str(&entry.name);
|
||||
} else {
|
||||
// the new length is the old length minus the combined length of the characters we want to cut off
|
||||
let i = entry.name.len()
|
||||
- entry
|
||||
.name
|
||||
.chars()
|
||||
.rev()
|
||||
.take(entry.name_charlen - filenamelen)
|
||||
.map(|char| char.len_utf8())
|
||||
.sum::<usize>();
|
||||
text.push_str(&entry.name[0..i.saturating_sub(3)]);
|
||||
text.push_str("...");
|
||||
}
|
||||
text.push(' ');
|
||||
text.push_str(&size);
|
||||
text.push(' ');
|
||||
text.push(endchar);
|
||||
vec![text.stylize()]
|
||||
}
|
||||
DirContentType::Symlink { metadata } => {
|
||||
let filenamelen = share.size.0 as usize - 2 - text_charlen;
|
||||
if entry.name_charlen < filenamelen {
|
||||
text.push_str(&entry.name);
|
||||
for _ in 0..(filenamelen - entry.name_charlen) {
|
||||
text.push(' ');
|
||||
}
|
||||
} else if entry.name_charlen == filenamelen {
|
||||
text.push_str(&entry.name);
|
||||
} else {
|
||||
// the new length is the old length minus the combined length of the characters we want to cut off
|
||||
let i = entry.name.len()
|
||||
- entry
|
||||
.name
|
||||
.chars()
|
||||
.rev()
|
||||
.take(entry.name_charlen - filenamelen)
|
||||
.map(|char| char.len_utf8())
|
||||
.sum::<usize>();
|
||||
text.push_str(&entry.name[0..i.saturating_sub(3)]);
|
||||
text.push_str("...");
|
||||
}
|
||||
text.push(' ');
|
||||
text.push(endchar);
|
||||
vec![text.italic()]
|
||||
}
|
||||
};
|
||||
queue!(share.stdout, cursor::MoveToNextLine(1))?;
|
||||
for mut s in styled {
|
||||
if entry.selected {
|
||||
s = s.bold();
|
||||
}
|
||||
queue!(share.stdout, style::PrintStyledContent(s))?;
|
||||
}
|
||||
}
|
||||
let empty_lines = self.last_drawn_files_count.saturating_sub(drawn_files);
|
||||
self.last_drawn_files_count = drawn_files;
|
||||
let empty_line = " ".repeat(share.size.0 as _);
|
||||
for _ in 0..empty_lines {
|
||||
queue!(
|
||||
share.stdout,
|
||||
cursor::MoveToNextLine(1),
|
||||
style::PrintStyledContent(empty_line.as_str().stylize())
|
||||
)?;
|
||||
}
|
||||
}
|
||||
if self.updates.redraw_searchbar() {
|
||||
self.updates.dont_redraw_searchbar();
|
||||
self.updates.request_move_cursor();
|
||||
let mut text = if self.search_text.len() > share.size.0 as _ {
|
||||
self.search_text[(self.search_text.len() - share.size.0 as usize)..]
|
||||
.to_string()
|
||||
} else {
|
||||
self.search_text.clone()
|
||||
};
|
||||
while text.len() < share.size.0 as _ {
|
||||
text.push(' ');
|
||||
}
|
||||
queue!(
|
||||
share.stdout,
|
||||
cursor::MoveTo(0, share.size.1 - 1),
|
||||
style::PrintStyledContent(text.underlined())
|
||||
);
|
||||
}
|
||||
if self.updates.move_cursor() {
|
||||
self.updates.dont_move_cursor();
|
||||
match self.focus {
|
||||
Focus::Files => {
|
||||
if self
|
||||
.dir_content
|
||||
.get(self.current_index)
|
||||
.is_some_and(|e| e.passes_filter)
|
||||
{
|
||||
let height = self
|
||||
.dir_content
|
||||
.iter()
|
||||
.skip(self.scroll)
|
||||
.take(self.current_index.saturating_sub(self.scroll))
|
||||
.filter(|e| e.passes_filter)
|
||||
.count();
|
||||
if height < self.last_drawn_files_height {
|
||||
queue!(share.stdout, cursor::MoveTo(0, 2 + height as u16))?;
|
||||
} else {
|
||||
queue!(share.stdout, cursor::MoveTo(0, 1))?;
|
||||
}
|
||||
} else {
|
||||
queue!(share.stdout, cursor::MoveTo(0, 1))?;
|
||||
}
|
||||
}
|
||||
Focus::SearchBar => {
|
||||
queue!(
|
||||
share.stdout,
|
||||
cursor::MoveTo(self.search_text.len() as _, share.size.1 - 1)
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// end of draw
|
||||
share.stdout.flush()?;
|
||||
// events
|
||||
if poll(Duration::from_millis(100))? {
|
||||
match read()? {
|
||||
Event::FocusGained => {}
|
||||
Event::FocusLost => {}
|
||||
Event::Mouse(e) => match (e.kind, e.column, e.row, e.modifiers) {
|
||||
_ => {}
|
||||
},
|
||||
Event::Key(e) => match (&self.focus, e.code) {
|
||||
// - - - Global - - -
|
||||
// Ctrl+Left/H -> Close
|
||||
(_, KeyCode::Left | KeyCode::Char('h'))
|
||||
if e.modifiers == KeyModifiers::CONTROL =>
|
||||
{
|
||||
return Ok(AppCmd::CloseInstance);
|
||||
}
|
||||
// Ctrl+Right/L -> Duplicate
|
||||
(_, KeyCode::Right | KeyCode::Char('l'))
|
||||
if e.modifiers == KeyModifiers::CONTROL =>
|
||||
{
|
||||
return Ok(AppCmd::AddInstance(self.clone()));
|
||||
}
|
||||
// Ctrl+Up/K -> Prev
|
||||
(_, KeyCode::Up | KeyCode::Char('k'))
|
||||
if e.modifiers == KeyModifiers::CONTROL =>
|
||||
{
|
||||
return Ok(AppCmd::PrevInstance);
|
||||
}
|
||||
// Ctrl+Down/J -> Next
|
||||
(_, KeyCode::Down | KeyCode::Char('j'))
|
||||
if e.modifiers == KeyModifiers::CONTROL =>
|
||||
{
|
||||
return Ok(AppCmd::NextInstance);
|
||||
}
|
||||
// - - - Files - - -
|
||||
// Down/J -> Down
|
||||
(Focus::Files, KeyCode::Down | KeyCode::Char('j')) => {
|
||||
self.set_current_index_to_visible(self.current_index + 1, true)
|
||||
}
|
||||
// Up/K -> Up
|
||||
(Focus::Files, KeyCode::Up | KeyCode::Char('k')) => {
|
||||
if self.current_index > 0 {
|
||||
self.set_current_index_to_visible(self.current_index - 1, false)
|
||||
}
|
||||
}
|
||||
// Left/H -> Leave Directory
|
||||
(Focus::Files, KeyCode::Left | KeyCode::Char('h')) => {
|
||||
// leave directory
|
||||
if let Some(this_dir) = self
|
||||
.current_dir
|
||||
.file_name()
|
||||
.map(|name| name.to_string_lossy().into_owned())
|
||||
{
|
||||
self.current_dir.pop();
|
||||
self.updates.request_redraw_infobar();
|
||||
self.request_rescan_files_then_select_by_name(this_dir);
|
||||
}
|
||||
}
|
||||
// Right/L -> Enter Directory
|
||||
(Focus::Files, KeyCode::Right | KeyCode::Char('l')) => {
|
||||
// descend into directory
|
||||
if let Some(entry) = self.dir_content.get(self.current_index) {
|
||||
self.current_dir = entry.entry.path();
|
||||
self.updates = u32::MAX;
|
||||
}
|
||||
}
|
||||
// A -> Select All
|
||||
(Focus::Files, KeyCode::Char('a')) => {
|
||||
self.updates.request_redraw_filelist();
|
||||
for e in &mut self.dir_content {
|
||||
if e.passes_filter {
|
||||
e.selected = !e.selected;
|
||||
}
|
||||
}
|
||||
}
|
||||
// S -> Toggle Select
|
||||
(Focus::Files, KeyCode::Char('s')) => {
|
||||
self.updates.request_redraw_filelist();
|
||||
if let Some(e) = self.dir_content.get_mut(self.current_index) {
|
||||
e.selected = !e.selected;
|
||||
}
|
||||
}
|
||||
// D -> Deselect All
|
||||
(Focus::Files, KeyCode::Char('d')) => {
|
||||
self.updates.request_redraw_filelist();
|
||||
for e in &mut self.dir_content {
|
||||
if e.passes_filter {
|
||||
e.selected = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
(Focus::Files, KeyCode::Char('f')) => {
|
||||
self.focus = Focus::SearchBar;
|
||||
self.updates.request_move_cursor();
|
||||
}
|
||||
// N -> New Directory
|
||||
(Focus::Files, KeyCode::Char('n')) => {
|
||||
let dir = self.current_dir.join(&self.search_text);
|
||||
if fs::create_dir_all(&dir).is_ok() {
|
||||
self.updates.request_reset_search();
|
||||
self.current_dir = dir;
|
||||
self.updates.request_redraw_infobar();
|
||||
}
|
||||
self.updates.request_rescan_files();
|
||||
}
|
||||
// C -> Copy
|
||||
(Focus::Files, KeyCode::Char('c')) => {
|
||||
if let Some(e) = self.dir_content.get(self.current_index) {
|
||||
if let DirContentType::Dir { .. } = e.more {
|
||||
return Ok(AppCmd::CopyTo(e.entry.path()));
|
||||
}
|
||||
}
|
||||
}
|
||||
// R -> Remove
|
||||
(Focus::Files, KeyCode::Char('r')) => {
|
||||
let paths = self
|
||||
.dir_content
|
||||
.iter()
|
||||
.rev()
|
||||
.filter(|e| e.selected)
|
||||
.map(|e| e.entry.path())
|
||||
.collect();
|
||||
tasks::task_del(paths, share);
|
||||
}
|
||||
// T -> Open Terminal
|
||||
(Focus::Files, KeyCode::Char('t')) => 'term: {
|
||||
Command::new(&share.terminal_command)
|
||||
.current_dir(&self.current_dir)
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.spawn();
|
||||
}
|
||||
// E -> Edit
|
||||
(Focus::Files, KeyCode::Char('e')) => {
|
||||
Self::term_reset(share)?;
|
||||
if let Some(entry) = self.dir_content.get(self.current_index) {
|
||||
let entry_path = entry.entry.path();
|
||||
Command::new(&share.editor_command)
|
||||
.arg(&entry_path)
|
||||
.current_dir(&self.current_dir)
|
||||
.status();
|
||||
}
|
||||
self.term_setup(share)?;
|
||||
}
|
||||
// 0-9 -> set scan_files_max_depth
|
||||
(Focus::Files, KeyCode::Char('0')) => {
|
||||
self.scan_files_max_depth = usize::MAX;
|
||||
self.request_rescan_files_then_select_current_again();
|
||||
}
|
||||
(Focus::Files, KeyCode::Char('1')) => {
|
||||
self.scan_files_max_depth = 0;
|
||||
self.request_rescan_files_then_select_current_again();
|
||||
}
|
||||
(Focus::Files, KeyCode::Char('2')) => {
|
||||
self.scan_files_max_depth = 1;
|
||||
self.request_rescan_files_then_select_current_again();
|
||||
}
|
||||
(Focus::Files, KeyCode::Char('3')) => {
|
||||
self.scan_files_max_depth = 2;
|
||||
self.request_rescan_files_then_select_current_again();
|
||||
}
|
||||
(Focus::Files, KeyCode::Char('4')) => {
|
||||
self.scan_files_max_depth = 3;
|
||||
self.request_rescan_files_then_select_current_again();
|
||||
}
|
||||
(Focus::Files, KeyCode::Char('5')) => {
|
||||
self.scan_files_max_depth = 4;
|
||||
self.request_rescan_files_then_select_current_again();
|
||||
}
|
||||
(Focus::Files, KeyCode::Char('6')) => {
|
||||
self.scan_files_max_depth = 5;
|
||||
self.request_rescan_files_then_select_current_again();
|
||||
}
|
||||
(Focus::Files, KeyCode::Char('7')) => {
|
||||
self.scan_files_max_depth = 6;
|
||||
self.request_rescan_files_then_select_current_again();
|
||||
}
|
||||
(Focus::Files, KeyCode::Char('8')) => {
|
||||
self.scan_files_max_depth = 7;
|
||||
self.request_rescan_files_then_select_current_again();
|
||||
}
|
||||
(Focus::Files, KeyCode::Char('9')) => {
|
||||
self.scan_files_max_depth = 8;
|
||||
self.request_rescan_files_then_select_current_again();
|
||||
}
|
||||
// - - - SearchBar - - -
|
||||
// Esc -> Nevermind
|
||||
(Focus::SearchBar, KeyCode::Esc) => {
|
||||
self.focus = Focus::Files;
|
||||
self.search_text.clear();
|
||||
self.search_regex = None;
|
||||
self.updates.request_redraw_searchbar();
|
||||
self.updates.request_move_cursor();
|
||||
if share.live_search {
|
||||
self.updates.request_filter_files();
|
||||
}
|
||||
}
|
||||
// Enter -> Apply
|
||||
(Focus::SearchBar, KeyCode::Enter) => {
|
||||
self.focus = Focus::Files;
|
||||
self.updates.request_move_cursor();
|
||||
if !share.live_search {
|
||||
self.updates.request_filter_files();
|
||||
}
|
||||
self.updates.request_reset_current_index();
|
||||
}
|
||||
(Focus::SearchBar, KeyCode::Char(ch)) => {
|
||||
self.search_text.push(ch);
|
||||
self.search_regex = None;
|
||||
self.updates.request_redraw_searchbar();
|
||||
if share.live_search {
|
||||
self.updates.request_filter_files();
|
||||
}
|
||||
}
|
||||
(Focus::SearchBar, KeyCode::Backspace) => {
|
||||
self.search_text.pop();
|
||||
self.search_regex = None;
|
||||
self.updates.request_redraw_searchbar();
|
||||
if share.live_search {
|
||||
self.updates.request_filter_files();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Event::Paste(e) => {}
|
||||
Event::Resize(w, h) => {
|
||||
share.size.0 = w;
|
||||
share.size.1 = h;
|
||||
self.updates.request_redraw();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
95
src/tasks.rs
Executable file
95
src/tasks.rs
Executable file
@@ -0,0 +1,95 @@
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
fs, io,
|
||||
path::{Path, PathBuf},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use crate::{updates::Updates, BackgroundTask, Share, TuiFile};
|
||||
|
||||
pub(crate) fn task_copy(
|
||||
src: Vec<(PathBuf, Vec<(PathBuf, bool)>)>,
|
||||
target: PathBuf,
|
||||
share: &mut Share,
|
||||
) {
|
||||
share.tasks.push(BackgroundTask::new(move |status| {
|
||||
let mut total: usize = src.iter().map(|v| v.1.len()).sum();
|
||||
for (parent, rel_paths) in src {
|
||||
let mut created: HashSet<PathBuf> = HashSet::new();
|
||||
for (rel_path, copy_recursive) in rel_paths {
|
||||
total = total.saturating_sub(1);
|
||||
{
|
||||
let s = format!("cp {total}");
|
||||
*status.lock().unwrap() = s;
|
||||
}
|
||||
let file_from = parent.join(&rel_path);
|
||||
let file_to = target.join(&rel_path);
|
||||
let is_dir = file_from.is_dir();
|
||||
let parent_created = if let Some(parent) = rel_path.parent() {
|
||||
parent.as_os_str().is_empty() || created.contains(parent)
|
||||
} else {
|
||||
true
|
||||
};
|
||||
if parent_created {
|
||||
if is_dir {
|
||||
copy_dir(file_from, file_to, copy_recursive);
|
||||
created.insert(rel_path);
|
||||
} else {
|
||||
fs::copy(&file_from, &file_to);
|
||||
}
|
||||
} else {
|
||||
let rel_path = rel_path.file_name().unwrap();
|
||||
let file_to = target.join(&rel_path);
|
||||
if is_dir {
|
||||
copy_dir(file_from, file_to, copy_recursive);
|
||||
created.insert(rel_path.into());
|
||||
} else {
|
||||
fs::copy(&file_from, &file_to);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}));
|
||||
}
|
||||
fn copy_dir(
|
||||
file_from: impl AsRef<Path>,
|
||||
file_to: impl AsRef<Path>,
|
||||
recursive: bool,
|
||||
) -> io::Result<()> {
|
||||
fs::create_dir(&file_to)?;
|
||||
if recursive {
|
||||
if let Ok(e) = fs::read_dir(file_from) {
|
||||
for e in e {
|
||||
if let Ok(e) = e {
|
||||
let p = e.path();
|
||||
let t = file_to.as_ref().join(e.file_name());
|
||||
if p.is_dir() {
|
||||
copy_dir(p, t, recursive);
|
||||
} else {
|
||||
fs::copy(&p, &t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn task_del(paths: Vec<PathBuf>, share: &mut Share) {
|
||||
share.tasks.push(BackgroundTask::new(move |status| {
|
||||
let mut total: usize = paths.len();
|
||||
for path in paths {
|
||||
{
|
||||
let s = format!("rm {total}");
|
||||
*status.lock().unwrap() = s;
|
||||
}
|
||||
if path.is_dir() {
|
||||
fs::remove_dir(path);
|
||||
} else {
|
||||
fs::remove_file(path);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}));
|
||||
}
|
||||
126
src/updates.rs
Executable file
126
src/updates.rs
Executable file
@@ -0,0 +1,126 @@
|
||||
pub(crate) trait Updates {
|
||||
fn request_redraw(&mut self) {
|
||||
self.request_redraw_infobar();
|
||||
self.request_redraw_searchbar();
|
||||
self.request_redraw_filelist();
|
||||
}
|
||||
|
||||
fn rescan_files(&self) -> bool;
|
||||
fn dont_rescan_files(&mut self);
|
||||
fn request_rescan_files(&mut self);
|
||||
|
||||
fn clear(&self) -> bool;
|
||||
fn dont_clear(&mut self);
|
||||
fn request_clear(&mut self);
|
||||
|
||||
fn redraw_infobar(&self) -> bool;
|
||||
fn dont_redraw_infobar(&mut self);
|
||||
fn request_redraw_infobar(&mut self);
|
||||
|
||||
fn redraw_searchbar(&self) -> bool;
|
||||
fn dont_redraw_searchbar(&mut self);
|
||||
fn request_redraw_searchbar(&mut self);
|
||||
|
||||
fn redraw_filelist(&self) -> bool;
|
||||
fn dont_redraw_filelist(&mut self);
|
||||
fn request_redraw_filelist(&mut self);
|
||||
|
||||
fn move_cursor(&self) -> bool;
|
||||
fn dont_move_cursor(&mut self);
|
||||
fn request_move_cursor(&mut self);
|
||||
|
||||
fn filter_files(&self) -> bool;
|
||||
fn dont_filter_files(&mut self);
|
||||
fn request_filter_files(&mut self);
|
||||
|
||||
fn reset_current_index(&self) -> bool;
|
||||
fn dont_reset_current_index(&mut self);
|
||||
fn request_reset_current_index(&mut self);
|
||||
|
||||
fn reset_search(&self) -> bool;
|
||||
fn dont_reset_search(&mut self);
|
||||
fn request_reset_search(&mut self);
|
||||
}
|
||||
impl Updates for u32 {
|
||||
fn rescan_files(&self) -> bool {
|
||||
0 != self & 0b1
|
||||
}
|
||||
fn dont_rescan_files(&mut self) {
|
||||
*self ^= 0b1;
|
||||
}
|
||||
fn request_rescan_files(&mut self) {
|
||||
*self |= 0b1;
|
||||
}
|
||||
fn clear(&self) -> bool {
|
||||
0 != self & 0b10
|
||||
}
|
||||
fn dont_clear(&mut self) {
|
||||
*self ^= 0b10;
|
||||
}
|
||||
fn request_clear(&mut self) {
|
||||
*self |= 0b10;
|
||||
}
|
||||
fn redraw_infobar(&self) -> bool {
|
||||
0 != self & 0b100
|
||||
}
|
||||
fn dont_redraw_infobar(&mut self) {
|
||||
*self ^= 0b100;
|
||||
}
|
||||
fn request_redraw_infobar(&mut self) {
|
||||
*self |= 0b100;
|
||||
}
|
||||
fn redraw_searchbar(&self) -> bool {
|
||||
0 != self & 0b1000
|
||||
}
|
||||
fn dont_redraw_searchbar(&mut self) {
|
||||
*self ^= 0b1000;
|
||||
}
|
||||
fn request_redraw_searchbar(&mut self) {
|
||||
*self |= 0b1000;
|
||||
}
|
||||
fn redraw_filelist(&self) -> bool {
|
||||
0 != self & 0b10000
|
||||
}
|
||||
fn dont_redraw_filelist(&mut self) {
|
||||
*self ^= 0b10000;
|
||||
}
|
||||
fn request_redraw_filelist(&mut self) {
|
||||
*self |= 0b10000;
|
||||
}
|
||||
fn move_cursor(&self) -> bool {
|
||||
0 != self & 0b100000
|
||||
}
|
||||
fn dont_move_cursor(&mut self) {
|
||||
*self ^= 0b100000;
|
||||
}
|
||||
fn request_move_cursor(&mut self) {
|
||||
*self |= 0b100000;
|
||||
}
|
||||
fn filter_files(&self) -> bool {
|
||||
0 != self & 0b1000000
|
||||
}
|
||||
fn dont_filter_files(&mut self) {
|
||||
*self ^= 0b1000000;
|
||||
}
|
||||
fn request_filter_files(&mut self) {
|
||||
*self |= 0b1000000;
|
||||
}
|
||||
fn reset_current_index(&self) -> bool {
|
||||
0 != self & 0b10000000
|
||||
}
|
||||
fn dont_reset_current_index(&mut self) {
|
||||
*self ^= 0b10000000;
|
||||
}
|
||||
fn request_reset_current_index(&mut self) {
|
||||
*self |= 0b10000000;
|
||||
}
|
||||
fn reset_search(&self) -> bool {
|
||||
0 != self & 0b100000000
|
||||
}
|
||||
fn dont_reset_search(&mut self) {
|
||||
*self ^= 0b100000000;
|
||||
}
|
||||
fn request_reset_search(&mut self) {
|
||||
*self |= 0b100000000;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user