mirror of
https://github.com/sxyazi/yazi.git
synced 2024-12-25 09:46:37 +03:00
feat: fix and enhance unset mode (#27)
This commit is contained in:
parent
badcf99416
commit
e52547de10
75
Cargo.lock
generated
75
Cargo.lock
generated
@ -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",
|
||||
]
|
||||
|
@ -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")),
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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();
|
||||
|
@ -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()) }
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user