initial commit

This commit is contained in:
Mark 2023-08-27 05:09:40 +02:00
commit 009debaeb7
8 changed files with 1801 additions and 0 deletions

1
.gitignore vendored Executable file
View File

@ -0,0 +1 @@
/target

461
Cargo.lock generated Executable file
View File

@ -0,0 +1,461 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a"
dependencies = [
"memchr",
]
[[package]]
name = "anstream"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea"
[[package]]
name = "anstyle-parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd"
dependencies = [
"anstyle",
"windows-sys",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d5f1946157a96594eb2d2c10eb7ad9a2b27518cb3000209dec700c35df9197d"
dependencies = [
"clap_builder",
"clap_derive",
"once_cell",
]
[[package]]
name = "clap_builder"
version = "4.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78116e32a042dd73c2901f0dc30790d20ff3447f3e3472fad359e8c3d282bcd6"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9fd1a5729c4548118d7d70ff234a44868d00489a4b6597b0b020918a0e91a1a"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961"
[[package]]
name = "colorchoice"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
name = "crossterm"
version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
dependencies = [
"bitflags 2.4.0",
"crossterm_winapi",
"libc",
"mio",
"parking_lot",
"signal-hook",
"signal-hook-mio",
"winapi",
]
[[package]]
name = "crossterm_winapi"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
dependencies = [
"winapi",
]
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "libc"
version = "0.2.147"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
[[package]]
name = "lock_api"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "mio"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
dependencies = [
"libc",
"log",
"wasi",
"windows-sys",
]
[[package]]
name = "once_cell"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]]
name = "parking_lot"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-targets",
]
[[package]]
name = "proc-macro2"
version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "regex"
version = "1.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "signal-hook"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
dependencies = [
"libc",
"signal-hook-registry",
]
[[package]]
name = "signal-hook-mio"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
dependencies = [
"libc",
"mio",
"signal-hook",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
dependencies = [
"libc",
]
[[package]]
name = "smallvec"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9"
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "2.0.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tuifile"
version = "0.1.0"
dependencies = [
"clap",
"crossterm",
"regex",
]
[[package]]
name = "unicode-ident"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
[[package]]
name = "utf8parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"

11
Cargo.toml Executable file
View File

@ -0,0 +1,11 @@
[package]
name = "tuifile"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
clap = { version = "4.4.0", features = ["derive"] }
crossterm = "0.27.0"
regex = "1.9.4"

14
README.md Executable file
View File

@ -0,0 +1,14 @@
# TuiFile
A file explorer for your terminal, with homerow-centric navigation.
TuiFile can
- have multiple instances, one for each open directory
- display recursive directory structures
- filter files using regex
- select multiple files at once
- create new directories
- copy, move and delete
- quickly open your `$TERM` and `$EDITOR`
- add more features (open an issue with ideas if you have any)

392
src/main.rs Executable file
View 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
View 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
View 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
View 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;
}
}