feat: fix and enhance unset mode (#27)

This commit is contained in:
三咲雅 · Misaki Masa 2023-08-07 13:31:24 +08:00 committed by GitHub
parent badcf99416
commit e52547de10
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 128 additions and 84 deletions

75
Cargo.lock generated
View File

@ -75,7 +75,7 @@ dependencies = [
"anyhow",
"config",
"core",
"crossterm",
"crossterm 0.27.0",
"futures",
"libc",
"ratatui",
@ -238,7 +238,7 @@ name = "config"
version = "0.1.0"
dependencies = [
"anyhow",
"crossterm",
"crossterm 0.27.0",
"futures",
"glob",
"once_cell",
@ -257,7 +257,7 @@ dependencies = [
"anyhow",
"async-channel",
"config",
"crossterm",
"crossterm 0.27.0",
"futures",
"indexmap 2.0.0",
"notify",
@ -341,6 +341,22 @@ checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13"
dependencies = [
"bitflags 1.3.2",
"crossterm_winapi",
"libc",
"mio",
"parking_lot",
"signal-hook",
"signal-hook-mio",
"winapi",
]
[[package]]
name = "crossterm"
version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
dependencies = [
"bitflags 2.3.3",
"crossterm_winapi",
"futures-core",
"libc",
"mio",
@ -436,13 +452,13 @@ dependencies = [
[[package]]
name = "filetime"
version = "0.2.21"
version = "0.2.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153"
checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0"
dependencies = [
"cfg-if",
"libc",
"redox_syscall 0.2.16",
"redox_syscall",
"windows-sys 0.48.0",
]
@ -778,9 +794,9 @@ dependencies = [
[[package]]
name = "kqueue"
version = "1.0.7"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c8fc60ba15bf51257aa9807a48a61013db043fcf3a78cb0d916e8e396dcad98"
checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c"
dependencies = [
"kqueue-sys",
"libc",
@ -788,9 +804,9 @@ dependencies = [
[[package]]
name = "kqueue-sys"
version = "1.0.3"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8367585489f01bc55dd27404dcf56b95e6da061a256a666ab23be9ba96a2e587"
checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b"
dependencies = [
"bitflags 1.3.2",
"libc",
@ -1062,7 +1078,7 @@ checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447"
dependencies = [
"cfg-if",
"libc",
"redox_syscall 0.3.5",
"redox_syscall",
"smallvec",
"windows-targets 0.48.1",
]
@ -1081,18 +1097,18 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
[[package]]
name = "pin-project"
version = "1.1.2"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "030ad2bc4db10a8944cb0d837f158bdfec4d4a4873ab701a95046770d11f8842"
checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.1.2"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c"
checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
dependencies = [
"proc-macro2",
"quote",
@ -1101,9 +1117,9 @@ dependencies = [
[[package]]
name = "pin-project-lite"
version = "0.2.10"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57"
checksum = "2c516611246607d0c04186886dbb3a754368ef82c79e9827a802c6d836dd111c"
[[package]]
name = "pin-utils"
@ -1188,7 +1204,7 @@ checksum = "8285baa38bdc9f879d92c0e37cb562ef38aa3aeefca22b3200186bc39242d3d5"
dependencies = [
"bitflags 2.3.3",
"cassowary",
"crossterm",
"crossterm 0.26.1",
"indoc",
"paste",
"unicode-segmentation",
@ -1217,15 +1233,6 @@ dependencies = [
"num_cpus",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "redox_syscall"
version = "0.3.5"
@ -1276,18 +1283,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde"
version = "1.0.181"
version = "1.0.182"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d3e73c93c3240c0bda063c239298e633114c69a888c3e37ca8bb33f343e9890"
checksum = "bdb30a74471f5b7a1fa299f40b4bf1be93af61116df95465b2b5fc419331e430"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.181"
version = "1.0.182"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be02f6cb0cd3a5ec20bbcfbcbd749f57daddb1a0882dc2e46a6c236c90b977ed"
checksum = "6f4c2c6ea4bc09b5c419012eafcdb0fcef1d9119d626c8f3a0708a5b92d38a70"
dependencies = [
"proc-macro2",
"quote",
@ -1328,7 +1335,7 @@ name = "shared"
version = "0.1.0"
dependencies = [
"anyhow",
"crossterm",
"crossterm 0.27.0",
"libc",
"parking_lot",
"ratatui",
@ -2005,9 +2012,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
[[package]]
name = "winnow"
version = "0.5.3"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f46aab759304e4d7b2075a9aecba26228bb073ee8c50db796b2c72c676b5d807"
checksum = "acaaa1190073b2b101e15083c38ee8ec891b5e05cbee516521e94ec008f61e64"
dependencies = [
"memchr",
]

View File

@ -76,11 +76,11 @@ impl Executor {
let state = exec.named.get("state").cloned().unwrap_or("none".to_string());
cx.manager.active_mut().select(optional_bool(&state))
}
"visual_mode" => cx.manager.active_mut().visual_mode(exec.named.contains_key("unset")),
"select_all" => {
let state = exec.named.get("state").cloned().unwrap_or("none".to_string());
cx.manager.active_mut().select_all(optional_bool(&state))
}
"visual_mode" => cx.manager.active_mut().visual_mode(exec.named.contains_key("unset")),
// Operation
"open" => cx.manager.open(exec.named.contains_key("interactive")),

View File

@ -15,7 +15,7 @@ pub(super) struct Folder<'a> {
impl<'a> Folder<'a> {
pub(super) fn new(cx: &'a Ctx, folder: &'a core::manager::Folder) -> Self {
Self { cx, folder, is_preview: false, is_selection: true }
Self { cx, folder, is_preview: false, is_selection: false }
}
#[inline]
@ -45,6 +45,7 @@ impl<'a> Folder<'a> {
impl<'a> Widget for Folder<'a> {
fn render(self, area: Rect, buf: &mut Buffer) {
let window = self.folder.window();
let mode = self.cx.manager.active().mode();
let items = window
.iter()
@ -57,7 +58,9 @@ impl<'a> Widget for Folder<'a> {
.map(|x| x.display.as_ref())
.unwrap_or("");
if v.is_selected {
if (!self.is_selection && v.is_selected)
|| (self.is_selection && mode.pending(i, v.is_selected))
{
buf.set_style(
Rect { x: area.x.saturating_sub(1), y: i as u16 + 1, width: 1, height: 1 },
if self.is_selection {

View File

@ -1,4 +1,4 @@
use core::manager::{Mode, ALL_RATIO, CURRENT_RATIO, PARENT_RATIO, PREVIEW_RATIO};
use core::manager::{ALL_RATIO, CURRENT_RATIO, PARENT_RATIO, PREVIEW_RATIO};
use ratatui::{buffer::Buffer, layout::{self, Constraint, Direction, Rect}, widgets::{Block, Borders, Padding, Widget}};
@ -38,7 +38,7 @@ impl<'a> Widget for Layout<'a> {
// Current
Folder::new(self.cx, manager.current())
.with_selection(matches!(manager.active().mode(), Mode::Select(_)))
.with_selection(manager.active().mode().is_visual())
.render(chunks[1], buf);
// Preview

View File

@ -28,16 +28,16 @@
- `--state=false`: Deselect the current file.
- `--state=none`: Default, toggle the selection state of the current file.
- visual_mode: Enter visual mode (selection mode).
- `--unset`: Enter visual mode (unset mode).
- select_all
- `--state=true`: Select all files.
- `--state=false`: Deselect all files.
- `--state=none`: Default, toggle the selection state of all files.
- visual_mode: Enter visual mode (selection mode).
- `--unset`: Enter visual mode (unset mode).
### Operation
- open: Open the selected files.

View File

@ -47,7 +47,7 @@ impl TryFrom<String> for Key {
}
let mut key = Self::default();
if !s.starts_with("<") || !s.ends_with(">") {
if !s.starts_with('<') || !s.ends_with('>') {
let c = s.chars().next().unwrap();
key.code = KeyCode::Char(c);
key.shift = c.is_ascii_uppercase();

View File

@ -1,4 +1,4 @@
use std::{fs::Metadata, path::{Path, PathBuf}};
use std::{borrow::Cow, fs::Metadata, path::{Path, PathBuf}};
use anyhow::Result;
use tokio::fs;
@ -43,7 +43,5 @@ impl File {
}
#[inline]
pub fn name(&self) -> Option<String> {
self.path.file_name().map(|s| s.to_string_lossy().to_string())
}
pub fn name(&self) -> Option<Cow<str>> { self.path.file_name().map(|s| s.to_string_lossy()) }
}

View File

@ -180,12 +180,7 @@ impl Folder {
}
#[inline]
pub fn has_selected(&self) -> bool { self.files.iter().any(|(_, item)| item.is_selected) }
pub fn selected(&self) -> Option<Vec<&File>> {
let v = self.files.iter().filter(|(_, f)| f.is_selected).map(|(_, f)| f).collect::<Vec<_>>();
if v.is_empty() { None } else { Some(v) }
}
pub fn has_selected(&self) -> bool { self.files.iter().any(|(_, f)| f.is_selected) }
pub fn rect_current(&self, path: &Path) -> Option<Rect> {
let pos = self.position(path)? - self.offset;

View File

@ -98,8 +98,9 @@ impl Manager {
}
tokio::spawn(async move {
let result =
emit!(Input(InputOpt::top(format!("There are {tasks} tasks running, sure to quit? (y/N)"))));
let result = emit!(Input(InputOpt::top(format!(
"There are {tasks} tasks running, sure to quit? (y/N)"
))));
if let Ok(choice) = result.await {
if choice.to_lowercase() == "y" {
@ -199,7 +200,7 @@ impl Manager {
}
pub fn rename(&self) -> bool {
if self.current().has_selected() {
if self.in_selecting() {
return self.bulk_rename();
}
@ -360,8 +361,26 @@ impl Manager {
#[inline]
pub fn hovered(&self) -> Option<&File> { self.tabs.active().current.hovered.as_ref() }
#[inline]
pub fn selected(&self) -> Vec<&File> {
self.current().selected().or_else(|| self.hovered().map(|h| vec![h])).unwrap_or_default()
let mode = &self.active().mode;
let files = &self.current().files;
let selected: Vec<_> = if !mode.is_visual() {
files.iter().filter(|(_, f)| f.is_selected).map(|(_, f)| f).collect()
} else {
files
.iter()
.enumerate()
.filter(|(i, (_, f))| mode.pending(*i, f.is_selected))
.map(|(_, (_, f))| f)
.collect()
};
if selected.is_empty() { self.hovered().map(|h| vec![h]).unwrap_or_default() } else { selected }
}
#[inline]
pub fn in_selecting(&self) -> bool {
self.active().mode.is_visual() || self.current().has_selected()
}
}

View File

@ -1,13 +1,13 @@
use std::fmt::Display;
use std::{collections::BTreeSet, fmt::Display};
use config::theme::{self, ColorGroup};
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub enum Mode {
#[default]
Normal,
Select(usize),
Unset(usize),
Select(usize, BTreeSet<usize>),
Unset(usize, BTreeSet<usize>),
}
impl Mode {
@ -15,27 +15,50 @@ impl Mode {
pub fn color<'a>(&self, group: &'a ColorGroup) -> &'a theme::Color {
match *self {
Mode::Normal => &group.normal,
Mode::Select(_) => &group.select,
Mode::Unset(_) => &group.unset,
Mode::Select(..) => &group.select,
Mode::Unset(..) => &group.unset,
}
}
#[inline]
pub fn start(&self) -> Option<usize> {
pub fn visual(&self) -> Option<(usize, &BTreeSet<usize>)> {
match self {
Mode::Normal => None,
Mode::Select(n) => Some(*n),
Mode::Unset(n) => Some(*n),
Mode::Select(start, indices) => Some((*start, indices)),
Mode::Unset(start, indices) => Some((*start, indices)),
}
}
#[inline]
pub fn visual_mut(&mut self) -> Option<(usize, &mut BTreeSet<usize>)> {
match self {
Mode::Normal => None,
Mode::Select(start, indices) => Some((*start, indices)),
Mode::Unset(start, indices) => Some((*start, indices)),
}
}
#[inline]
pub fn pending(&self, idx: usize, state: bool) -> bool {
match self {
Mode::Normal => state,
Mode::Select(_, indices) => state || indices.contains(&idx),
Mode::Unset(_, indices) => state && !indices.contains(&idx),
}
}
}
impl Mode {
#[inline]
pub fn is_visual(&self) -> bool { matches!(self, Mode::Select(..) | Mode::Unset(..)) }
}
impl Display for Mode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self {
Mode::Normal => write!(f, "NORMAL"),
Mode::Select(_) => write!(f, "SELECT"),
Mode::Unset(_) => write!(f, "UN-SET"),
Mode::Select(..) => write!(f, "SELECT"),
Mode::Unset(..) => write!(f, "UN-SET"),
}
}
}

View File

@ -1,4 +1,4 @@
use std::{collections::BTreeMap, mem, path::{Path, PathBuf}};
use std::{collections::{BTreeMap, BTreeSet}, mem, path::{Path, PathBuf}};
use anyhow::{Error, Result};
use shared::Defer;
@ -33,7 +33,12 @@ impl Tab {
}
pub fn escape(&mut self) -> bool {
if matches!(self.mode, Mode::Select(_) | Mode::Unset(_)) {
if let Some((_, indices)) = self.mode.visual() {
let b = matches!(self.mode, Mode::Select(..));
for idx in indices.iter() {
self.current.select(Some(*idx), Some(b));
}
self.mode = Mode::Normal;
return true;
}
@ -46,7 +51,6 @@ impl Tab {
}
pub fn arrow(&mut self, step: isize) -> bool {
let before = self.current.cursor();
let ok = if step > 0 {
self.current.next(step as usize)
} else {
@ -57,15 +61,12 @@ impl Tab {
}
// Visual selection
if let Some(start) = self.mode.start() {
if let Some((start, items)) = self.mode.visual_mut() {
let after = self.current.cursor();
if (after > before && before < start) || (after < before && before > start) {
for i in before.min(start)..=start.max(before) {
self.current.select(Some(i), Some(false));
}
}
items.clear();
for i in start.min(after)..=after.max(start) {
self.current.select(Some(i), Some(true));
items.insert(i);
}
}
@ -251,11 +252,9 @@ impl Tab {
let idx = self.current.cursor();
if unset {
self.mode = Mode::Unset(idx);
self.current.select(Some(idx), Some(false));
self.mode = Mode::Unset(idx, BTreeSet::from([idx]));
} else {
self.mode = Mode::Select(idx);
self.current.select(Some(idx), Some(true));
self.mode = Mode::Select(idx, BTreeSet::from([idx]));
};
true
}