mirror of
https://github.com/Dummi26/tuifile.git
synced 2025-03-10 03:43:52 +01:00
initial commit
This commit is contained in:
commit
009debaeb7
1
.gitignore
vendored
Executable file
1
.gitignore
vendored
Executable file
@ -0,0 +1 @@
|
||||
/target
|
461
Cargo.lock
generated
Executable file
461
Cargo.lock
generated
Executable 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
11
Cargo.toml
Executable 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
14
README.md
Executable 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
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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user