mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
Merge branch 'main' into pr-a9c266c7
This commit is contained in:
commit
36fa82195e
93 changed files with 2067 additions and 1074 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
|
@ -2483,6 +2483,7 @@ version = "0.1.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-channel",
|
"async-channel",
|
||||||
|
"bitflags 2.4.1",
|
||||||
"clipboard-win",
|
"clipboard-win",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
"futures",
|
"futures",
|
||||||
|
|
@ -2556,10 +2557,12 @@ name = "yazi-shared"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"bitflags 2.4.1",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
"futures",
|
"futures",
|
||||||
"libc",
|
"libc",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
|
"percent-encoding",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
"regex",
|
"regex",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","️ Überzug","️ Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","fdlimit"],"language":"en","flagWords":[],"version":"0.2"}
|
{"language":"en","flagWords":[],"version":"0.2","words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","️ Überzug","️ Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE"]}
|
||||||
|
|
@ -217,6 +217,21 @@ keymap = [
|
||||||
{ on = [ "~" ], exec = "help", desc = "Open help" }
|
{ on = [ "~" ], exec = "help", desc = "Open help" }
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[completion]
|
||||||
|
|
||||||
|
keymap = [
|
||||||
|
{ on = [ "<C-q>" ], exec = "close", desc = "Cancel completion" },
|
||||||
|
{ on = [ "<Tab>" ], exec = "close --submit", desc = "Submit the completion" },
|
||||||
|
|
||||||
|
{ on = [ "<A-k>" ], exec = "arrow -1", desc = "Move cursor up" },
|
||||||
|
{ on = [ "<A-j>" ], exec = "arrow 1", desc = "Move cursor down" },
|
||||||
|
|
||||||
|
{ on = [ "<Up>" ], exec = "arrow -1", desc = "Move cursor up" },
|
||||||
|
{ on = [ "<Down>" ], exec = "arrow 1", desc = "Move cursor down" },
|
||||||
|
|
||||||
|
{ on = [ "~" ], exec = "help", desc = "Open help" }
|
||||||
|
]
|
||||||
|
|
||||||
[help]
|
[help]
|
||||||
|
|
||||||
keymap = [
|
keymap = [
|
||||||
|
|
@ -237,18 +252,3 @@ keymap = [
|
||||||
# Filtering
|
# Filtering
|
||||||
{ on = [ "/" ], exec = "filter", desc = "Apply a filter for the help items" },
|
{ on = [ "/" ], exec = "filter", desc = "Apply a filter for the help items" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[completion]
|
|
||||||
|
|
||||||
keymap = [
|
|
||||||
{ on = [ "<C-q>" ], exec = "close", desc = "Cancel completion" },
|
|
||||||
{ on = [ "<Enter>" ], exec = "close --submit", desc = "Submit the completion" },
|
|
||||||
|
|
||||||
{ on = [ "<A-k>" ], exec = "arrow -1", desc = "Move cursor up" },
|
|
||||||
{ on = [ "<A-j>" ], exec = "arrow 1", desc = "Move cursor down" },
|
|
||||||
|
|
||||||
{ on = [ "<Up>" ], exec = "arrow -1", desc = "Move cursor up" },
|
|
||||||
{ on = [ "<Down>" ], exec = "arrow 1", desc = "Move cursor down" },
|
|
||||||
|
|
||||||
{ on = [ "~" ], exec = "help", desc = "Open help" }
|
|
||||||
]
|
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,16 @@ permissions_s = { fg = "darkgray" }
|
||||||
# : }}}
|
# : }}}
|
||||||
|
|
||||||
|
|
||||||
|
# : Select {{{
|
||||||
|
|
||||||
|
[select]
|
||||||
|
border = { fg = "blue" }
|
||||||
|
active = { fg = "magenta" }
|
||||||
|
inactive = {}
|
||||||
|
|
||||||
|
# : }}}
|
||||||
|
|
||||||
|
|
||||||
# : Input {{{
|
# : Input {{{
|
||||||
|
|
||||||
[input]
|
[input]
|
||||||
|
|
@ -75,16 +85,6 @@ selected = { reversed = true }
|
||||||
# : }}}
|
# : }}}
|
||||||
|
|
||||||
|
|
||||||
# : Select {{{
|
|
||||||
|
|
||||||
[select]
|
|
||||||
border = { fg = "blue" }
|
|
||||||
active = { fg = "magenta" }
|
|
||||||
inactive = {}
|
|
||||||
|
|
||||||
# : }}}
|
|
||||||
|
|
||||||
|
|
||||||
# : Completion {{{
|
# : Completion {{{
|
||||||
|
|
||||||
[completion]
|
[completion]
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ yazi-shared = { path = "../yazi-shared", version = "0.1.5" }
|
||||||
# External dependencies
|
# External dependencies
|
||||||
anyhow = "^1"
|
anyhow = "^1"
|
||||||
async-channel = "^1"
|
async-channel = "^1"
|
||||||
|
bitflags = "^2"
|
||||||
crossterm = "^0"
|
crossterm = "^0"
|
||||||
futures = "^0"
|
futures = "^0"
|
||||||
indexmap = "^2"
|
indexmap = "^2"
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,14 @@ use yazi_config::keymap::Exec;
|
||||||
|
|
||||||
use crate::completion::Completion;
|
use crate::completion::Completion;
|
||||||
|
|
||||||
pub struct Opt(isize);
|
pub struct Opt {
|
||||||
|
step: isize,
|
||||||
|
}
|
||||||
|
|
||||||
impl From<&Exec> for Opt {
|
impl From<&Exec> for Opt {
|
||||||
fn from(e: &Exec) -> Self { Self(e.args.first().and_then(|s| s.parse().ok()).unwrap_or(0)) }
|
fn from(e: &Exec) -> Self {
|
||||||
|
Self { step: e.args.first().and_then(|s| s.parse().ok()).unwrap_or(0) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Completion {
|
impl Completion {
|
||||||
|
|
@ -38,7 +42,7 @@ impl Completion {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn arrow(&mut self, opt: impl Into<Opt>) -> bool {
|
pub fn arrow(&mut self, opt: impl Into<Opt>) -> bool {
|
||||||
let step = opt.into().0;
|
let opt = opt.into() as Opt;
|
||||||
if step > 0 { self.next(step as usize) } else { self.prev(step.unsigned_abs()) }
|
if opt.step > 0 { self.next(opt.step as usize) } else { self.prev(opt.step.unsigned_abs()) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,18 @@ use yazi_config::keymap::{Exec, KeymapLayer};
|
||||||
|
|
||||||
use crate::{completion::Completion, emit};
|
use crate::{completion::Completion, emit};
|
||||||
|
|
||||||
pub struct Opt(bool);
|
pub struct Opt {
|
||||||
|
submit: bool,
|
||||||
|
}
|
||||||
|
|
||||||
impl From<&Exec> for Opt {
|
impl From<&Exec> for Opt {
|
||||||
fn from(e: &Exec) -> Self { Self(e.named.contains_key("submit")) }
|
fn from(e: &Exec) -> Self { Self { submit: e.named.contains_key("submit") } }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Completion {
|
impl Completion {
|
||||||
pub fn close(&mut self, opt: impl Into<Opt>) -> bool {
|
pub fn close(&mut self, opt: impl Into<Opt>) -> bool {
|
||||||
let submit = opt.into().0;
|
let opt = opt.into() as Opt;
|
||||||
if submit {
|
if opt.submit {
|
||||||
emit!(Call(
|
emit!(Call(
|
||||||
Exec::call("complete", vec![self.selected().into()]).with("ticket", self.ticket).vec(),
|
Exec::call("complete", vec![self.selected().into()]).with("ticket", self.ticket).vec(),
|
||||||
KeymapLayer::Input
|
KeymapLayer::Input
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ impl<'a> From<&'a Exec> for Opt<'a> {
|
||||||
|
|
||||||
impl Completion {
|
impl Completion {
|
||||||
pub fn show<'a>(&mut self, opt: impl Into<Opt<'a>>) -> bool {
|
pub fn show<'a>(&mut self, opt: impl Into<Opt<'a>>) -> bool {
|
||||||
let opt = opt.into();
|
let opt = opt.into() as Opt;
|
||||||
if self.ticket != opt.ticket {
|
if self.ticket != opt.ticket {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ impl Completion {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn trigger<'a>(&mut self, opt: impl Into<Opt<'a>>) -> bool {
|
pub fn trigger<'a>(&mut self, opt: impl Into<Opt<'a>>) -> bool {
|
||||||
let opt = opt.into();
|
let opt = opt.into() as Opt;
|
||||||
if opt.ticket < self.ticket {
|
if opt.ticket < self.ticket {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ pub enum Event {
|
||||||
Call(Vec<Exec>, KeymapLayer),
|
Call(Vec<Exec>, KeymapLayer),
|
||||||
|
|
||||||
// Manager
|
// Manager
|
||||||
Cd(Url),
|
|
||||||
Refresh,
|
Refresh,
|
||||||
Files(FilesOp),
|
Files(FilesOp),
|
||||||
Pages(usize),
|
Pages(usize),
|
||||||
|
|
@ -74,9 +73,6 @@ macro_rules! emit {
|
||||||
$crate::Event::Call($exec, $layer).emit();
|
$crate::Event::Call($exec, $layer).emit();
|
||||||
};
|
};
|
||||||
|
|
||||||
(Cd($url:expr)) => {
|
|
||||||
$crate::Event::Cd($url).emit();
|
|
||||||
};
|
|
||||||
(Files($op:expr)) => {
|
(Files($op:expr)) => {
|
||||||
$crate::Event::Files($op).emit();
|
$crate::Event::Files($op).emit();
|
||||||
};
|
};
|
||||||
|
|
|
||||||
27
yazi-core/src/external/fzf.rs
vendored
27
yazi-core/src/external/fzf.rs
vendored
|
|
@ -1,27 +1,22 @@
|
||||||
use std::process::Stdio;
|
use std::{path::Path, process::Stdio};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::{bail, Result};
|
||||||
use tokio::{process::Command, sync::oneshot::{self, Receiver}};
|
use tokio::process::Command;
|
||||||
use yazi_shared::Url;
|
use yazi_shared::Url;
|
||||||
|
|
||||||
pub struct FzfOpt {
|
pub struct FzfOpt {
|
||||||
pub cwd: Url,
|
pub cwd: Url,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fzf(opt: FzfOpt) -> Result<Receiver<Result<Url>>> {
|
pub async fn fzf(opt: FzfOpt) -> Result<Url> {
|
||||||
let child =
|
let child =
|
||||||
Command::new("fzf").current_dir(&opt.cwd).kill_on_drop(true).stdout(Stdio::piped()).spawn()?;
|
Command::new("fzf").current_dir(&opt.cwd).kill_on_drop(true).stdout(Stdio::piped()).spawn()?;
|
||||||
|
|
||||||
let (tx, rx) = oneshot::channel();
|
let output = child.wait_with_output().await?;
|
||||||
tokio::spawn(async move {
|
let selected = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||||
if let Ok(output) = child.wait_with_output().await {
|
|
||||||
let selected = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
if selected.is_empty() {
|
||||||
if !selected.is_empty() {
|
bail!("No match")
|
||||||
tx.send(Ok(opt.cwd.join(selected))).ok();
|
}
|
||||||
return;
|
return Ok(Url::from(Path::new(&opt.cwd).join(selected)));
|
||||||
}
|
|
||||||
}
|
|
||||||
tx.send(Err(anyhow::anyhow!("No match"))).ok();
|
|
||||||
});
|
|
||||||
Ok(rx)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
25
yazi-core/src/external/zoxide.rs
vendored
25
yazi-core/src/external/zoxide.rs
vendored
|
|
@ -1,14 +1,14 @@
|
||||||
use std::process::Stdio;
|
use std::process::Stdio;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::{bail, Result};
|
||||||
use tokio::{process::Command, sync::oneshot::{self, Receiver}};
|
use tokio::process::Command;
|
||||||
use yazi_shared::Url;
|
use yazi_shared::Url;
|
||||||
|
|
||||||
pub struct ZoxideOpt {
|
pub struct ZoxideOpt {
|
||||||
pub cwd: Url,
|
pub cwd: Url,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn zoxide(opt: ZoxideOpt) -> Result<Receiver<Result<Url>>> {
|
pub async fn zoxide(opt: ZoxideOpt) -> Result<Url> {
|
||||||
let child = Command::new("zoxide")
|
let child = Command::new("zoxide")
|
||||||
.args(["query", "-i", "--exclude"])
|
.args(["query", "-i", "--exclude"])
|
||||||
.arg(&opt.cwd)
|
.arg(&opt.cwd)
|
||||||
|
|
@ -16,16 +16,11 @@ pub fn zoxide(opt: ZoxideOpt) -> Result<Receiver<Result<Url>>> {
|
||||||
.stdout(Stdio::piped())
|
.stdout(Stdio::piped())
|
||||||
.spawn()?;
|
.spawn()?;
|
||||||
|
|
||||||
let (tx, rx) = oneshot::channel();
|
let output = child.wait_with_output().await?;
|
||||||
tokio::spawn(async move {
|
let selected = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||||
if let Ok(output) = child.wait_with_output().await {
|
|
||||||
let selected = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
if !selected.is_empty() {
|
||||||
if !selected.is_empty() {
|
return Ok(Url::from(selected));
|
||||||
tx.send(Ok(Url::from(selected))).ok();
|
}
|
||||||
return;
|
bail!("No match")
|
||||||
}
|
|
||||||
}
|
|
||||||
tx.send(Err(anyhow::anyhow!("No match"))).ok();
|
|
||||||
});
|
|
||||||
Ok(rx)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,55 @@
|
||||||
use std::{borrow::Cow, collections::BTreeMap, ffi::OsStr, fs::Metadata};
|
use std::{borrow::Cow, collections::BTreeMap, ffi::OsStr, fs::Metadata, ops::Deref};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
use yazi_shared::Url;
|
use yazi_shared::{Cha, ChaMeta, Url};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct File {
|
pub struct File {
|
||||||
pub url: Url,
|
pub url: Url,
|
||||||
pub meta: Metadata,
|
pub cha: Cha,
|
||||||
pub(super) link_to: Option<Url>,
|
pub(super) link_to: Option<Url>,
|
||||||
pub is_link: bool,
|
}
|
||||||
pub is_hidden: bool,
|
|
||||||
|
impl Deref for File {
|
||||||
|
type Target = Cha;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn deref(&self) -> &Self::Target { &self.cha }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl File {
|
impl File {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub async fn from(url: Url) -> Result<Self> {
|
pub async fn from(url: Url) -> Result<Self> {
|
||||||
let meta = fs::metadata(&url).await?;
|
let meta = fs::symlink_metadata(&url).await?;
|
||||||
Ok(Self::from_meta(url, meta).await)
|
Ok(Self::from_meta(url, meta).await)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn from_meta(url: Url, mut meta: Metadata) -> Self {
|
pub async fn from_meta(url: Url, mut meta: Metadata) -> Self {
|
||||||
let is_link = meta.is_symlink();
|
let mut cm = ChaMeta::empty();
|
||||||
let mut link_to = None;
|
|
||||||
|
|
||||||
|
let (is_link, mut link_to) = (meta.is_symlink(), None);
|
||||||
if is_link {
|
if is_link {
|
||||||
|
cm |= ChaMeta::LINK;
|
||||||
meta = fs::metadata(&url).await.unwrap_or(meta);
|
meta = fs::metadata(&url).await.unwrap_or(meta);
|
||||||
link_to = fs::read_link(&url).await.map(Url::from).ok();
|
link_to = fs::read_link(&url).await.map(Url::from).ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
let is_hidden = url.file_name().map(|s| s.to_string_lossy().starts_with('.')).unwrap_or(false);
|
if is_link && meta.is_symlink() {
|
||||||
Self { url, meta, link_to, is_link, is_hidden }
|
cm |= ChaMeta::BAD_LINK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if url.is_hidden() {
|
||||||
|
cm |= ChaMeta::HIDDEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
Self { url, cha: Cha::from(meta).with_meta(cm), link_to }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn from_dummy(url: Url) -> Self {
|
||||||
|
let cm = if url.is_hidden() { ChaMeta::HIDDEN } else { ChaMeta::empty() };
|
||||||
|
Self { url, cha: Cha::default().with_meta(cm), link_to: None }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
@ -60,13 +79,6 @@ impl File {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn parent(&self) -> Option<Url> { self.url.parent_url() }
|
pub fn parent(&self) -> Option<Url> { self.url.parent_url() }
|
||||||
|
|
||||||
// --- Meta
|
|
||||||
#[inline]
|
|
||||||
pub fn is_file(&self) -> bool { self.meta.is_file() }
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn is_dir(&self) -> bool { self.meta.is_dir() }
|
|
||||||
|
|
||||||
// --- Link to / Is link
|
// --- Link to / Is link
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn link_to(&self) -> Option<&Url> { self.link_to.as_ref() }
|
pub fn link_to(&self) -> Option<&Url> { self.link_to.as_ref() }
|
||||||
|
|
|
||||||
|
|
@ -128,7 +128,7 @@ impl Files {
|
||||||
|
|
||||||
pub fn update_full(&mut self, mut items: Vec<File>) -> bool {
|
pub fn update_full(&mut self, mut items: Vec<File>) -> bool {
|
||||||
if !self.show_hidden {
|
if !self.show_hidden {
|
||||||
(self.hidden, items) = items.into_iter().partition(|f| f.is_hidden);
|
(self.hidden, items) = items.into_iter().partition(|f| f.is_hidden());
|
||||||
}
|
}
|
||||||
self.ticket = FILES_TICKET.fetch_add(1, Ordering::Relaxed);
|
self.ticket = FILES_TICKET.fetch_add(1, Ordering::Relaxed);
|
||||||
self.sorter.sort(&mut items, &self.sizes);
|
self.sorter.sort(&mut items, &self.sizes);
|
||||||
|
|
@ -146,7 +146,7 @@ impl Files {
|
||||||
if self.show_hidden {
|
if self.show_hidden {
|
||||||
self.items.extend(items);
|
self.items.extend(items);
|
||||||
} else {
|
} else {
|
||||||
let (hidden, items): (Vec<_>, Vec<_>) = items.into_iter().partition(|f| f.is_hidden);
|
let (hidden, items): (Vec<_>, Vec<_>) = items.into_iter().partition(|f| f.is_hidden());
|
||||||
self.items.extend(items);
|
self.items.extend(items);
|
||||||
self.hidden.extend(hidden);
|
self.hidden.extend(hidden);
|
||||||
}
|
}
|
||||||
|
|
@ -178,7 +178,7 @@ impl Files {
|
||||||
|
|
||||||
pub fn update_creating(&mut self, mut todo: BTreeMap<Url, File>) -> bool {
|
pub fn update_creating(&mut self, mut todo: BTreeMap<Url, File>) -> bool {
|
||||||
if !self.show_hidden {
|
if !self.show_hidden {
|
||||||
todo.retain(|_, f| !f.is_hidden);
|
todo.retain(|_, f| !f.is_hidden());
|
||||||
}
|
}
|
||||||
|
|
||||||
let b = self.update_replacing(&mut todo);
|
let b = self.update_replacing(&mut todo);
|
||||||
|
|
@ -335,7 +335,7 @@ impl Files {
|
||||||
self.sorter.sort(&mut self.items, &self.sizes);
|
self.sorter.sort(&mut self.items, &self.sizes);
|
||||||
} else {
|
} else {
|
||||||
let items = mem::take(&mut self.items);
|
let items = mem::take(&mut self.items);
|
||||||
(self.hidden, self.items) = items.into_iter().partition(|f| f.is_hidden);
|
(self.hidden, self.items) = items.into_iter().partition(|f| f.is_hidden());
|
||||||
}
|
}
|
||||||
|
|
||||||
self.show_hidden = state;
|
self.show_hidden = state;
|
||||||
|
|
|
||||||
|
|
@ -33,13 +33,13 @@ impl FilesSorter {
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
SortBy::Created => items.sort_unstable_by(|a, b| {
|
SortBy::Created => items.sort_unstable_by(|a, b| {
|
||||||
if let (Ok(aa), Ok(bb)) = (a.meta.created(), b.meta.created()) {
|
if let (Some(aa), Some(bb)) = (a.created, b.created) {
|
||||||
return self.cmp(aa, bb, self.promote(a, b));
|
return self.cmp(aa, bb, self.promote(a, b));
|
||||||
}
|
}
|
||||||
Ordering::Equal
|
Ordering::Equal
|
||||||
}),
|
}),
|
||||||
SortBy::Modified => items.sort_unstable_by(|a, b| {
|
SortBy::Modified => items.sort_unstable_by(|a, b| {
|
||||||
if let (Ok(aa), Ok(bb)) = (a.meta.modified(), b.meta.modified()) {
|
if let (Some(aa), Some(bb)) = (a.modified, b.modified) {
|
||||||
return self.cmp(aa, bb, self.promote(a, b));
|
return self.cmp(aa, bb, self.promote(a, b));
|
||||||
}
|
}
|
||||||
Ordering::Equal
|
Ordering::Equal
|
||||||
|
|
@ -48,7 +48,7 @@ impl FilesSorter {
|
||||||
SortBy::Size => items.sort_unstable_by(|a, b| {
|
SortBy::Size => items.sort_unstable_by(|a, b| {
|
||||||
let aa = if a.is_dir() { sizes.get(&a.url).copied() } else { None };
|
let aa = if a.is_dir() { sizes.get(&a.url).copied() } else { None };
|
||||||
let bb = if b.is_dir() { sizes.get(&b.url).copied() } else { None };
|
let bb = if b.is_dir() { sizes.get(&b.url).copied() } else { None };
|
||||||
self.cmp(aa.unwrap_or(a.meta.len()), bb.unwrap_or(b.meta.len()), self.promote(a, b))
|
self.cmp(aa.unwrap_or(a.len), bb.unwrap_or(b.len), self.promote(a, b))
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
|
|
@ -72,17 +72,9 @@ impl FilesSorter {
|
||||||
if self.reverse { ordering.reverse() } else { ordering }
|
if self.reverse { ordering.reverse() } else { ordering }
|
||||||
});
|
});
|
||||||
|
|
||||||
let dummy = File {
|
|
||||||
url: Default::default(),
|
|
||||||
meta: items[0].meta.clone(),
|
|
||||||
link_to: None,
|
|
||||||
is_link: false,
|
|
||||||
is_hidden: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut new = Vec::with_capacity(indices.len());
|
let mut new = Vec::with_capacity(indices.len());
|
||||||
for i in indices {
|
for i in indices {
|
||||||
new.push(mem::replace(&mut items[i], dummy.clone()));
|
new.push(mem::take(&mut items[i]));
|
||||||
}
|
}
|
||||||
*items = new;
|
*items = new;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
56
yazi-core/src/help/commands/arrow.rs
Normal file
56
yazi-core/src/help/commands/arrow.rs
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
use yazi_config::keymap::Exec;
|
||||||
|
|
||||||
|
use crate::help::Help;
|
||||||
|
|
||||||
|
pub struct Opt {
|
||||||
|
step: isize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Exec> for Opt {
|
||||||
|
fn from(e: &Exec) -> Self {
|
||||||
|
Self { step: e.args.first().and_then(|s| s.parse().ok()).unwrap_or(0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<isize> for Opt {
|
||||||
|
fn from(step: isize) -> Self { Self { step } }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Help {
|
||||||
|
#[inline]
|
||||||
|
pub fn arrow(&mut self, opt: impl Into<Opt>) -> bool {
|
||||||
|
let max = self.bindings.len().saturating_sub(1);
|
||||||
|
self.offset = self.offset.min(max);
|
||||||
|
self.cursor = self.cursor.min(max);
|
||||||
|
|
||||||
|
let opt = opt.into() as Opt;
|
||||||
|
if opt.step > 0 { self.next(opt.step as usize) } else { self.prev(opt.step.unsigned_abs()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next(&mut self, step: usize) -> bool {
|
||||||
|
let len = self.bindings.len();
|
||||||
|
if len == 0 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let old = self.cursor;
|
||||||
|
self.cursor = (self.cursor + step).min(len - 1);
|
||||||
|
|
||||||
|
let limit = Self::limit();
|
||||||
|
if self.cursor >= (self.offset + limit).min(len).saturating_sub(5) {
|
||||||
|
self.offset = len.saturating_sub(limit).min(self.offset + self.cursor - old);
|
||||||
|
}
|
||||||
|
|
||||||
|
old != self.cursor
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prev(&mut self, step: usize) -> bool {
|
||||||
|
let old = self.cursor;
|
||||||
|
self.cursor = self.cursor.saturating_sub(step);
|
||||||
|
|
||||||
|
if self.cursor < self.offset + 5 {
|
||||||
|
self.offset = self.offset.saturating_sub(old - self.cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
old != self.cursor
|
||||||
|
}
|
||||||
|
}
|
||||||
21
yazi-core/src/help/commands/escape.rs
Normal file
21
yazi-core/src/help/commands/escape.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
use yazi_config::keymap::Exec;
|
||||||
|
|
||||||
|
use crate::help::Help;
|
||||||
|
|
||||||
|
pub struct Opt;
|
||||||
|
|
||||||
|
impl From<&Exec> for Opt {
|
||||||
|
fn from(_: &Exec) -> Self { Self }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Help {
|
||||||
|
pub fn escape(&mut self, _: impl Into<Opt>) -> bool {
|
||||||
|
if self.in_filter.is_some() {
|
||||||
|
self.in_filter = None;
|
||||||
|
self.filter_apply();
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
self.toggle(self.layer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
yazi-core/src/help/commands/filter.rs
Normal file
17
yazi-core/src/help/commands/filter.rs
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
use yazi_config::keymap::Exec;
|
||||||
|
|
||||||
|
use crate::help::Help;
|
||||||
|
|
||||||
|
pub struct Opt;
|
||||||
|
|
||||||
|
impl From<&Exec> for Opt {
|
||||||
|
fn from(_: &Exec) -> Self { Self }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Help {
|
||||||
|
pub fn filter(&mut self, _: impl Into<Opt>) -> bool {
|
||||||
|
self.in_filter = Some(Default::default());
|
||||||
|
self.filter_apply();
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
3
yazi-core/src/help/commands/mod.rs
Normal file
3
yazi-core/src/help/commands/mod.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
mod arrow;
|
||||||
|
mod escape;
|
||||||
|
mod filter;
|
||||||
|
|
@ -7,16 +7,16 @@ use crate::{emit, input::Input};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Help {
|
pub struct Help {
|
||||||
pub visible: bool,
|
pub visible: bool,
|
||||||
pub layer: KeymapLayer,
|
pub layer: KeymapLayer,
|
||||||
bindings: Vec<Control>,
|
pub(super) bindings: Vec<Control>,
|
||||||
|
|
||||||
// Filter
|
// Filter
|
||||||
keyword: Option<String>,
|
keyword: Option<String>,
|
||||||
in_filter: Option<Input>,
|
pub(super) in_filter: Option<Input>,
|
||||||
|
|
||||||
offset: usize,
|
pub(super) offset: usize,
|
||||||
cursor: usize,
|
pub(super) cursor: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Help {
|
impl Help {
|
||||||
|
|
@ -38,60 +38,7 @@ impl Help {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn escape(&mut self) -> bool {
|
pub(super) fn filter_apply(&mut self) -> bool {
|
||||||
if self.in_filter.is_some() {
|
|
||||||
self.in_filter = None;
|
|
||||||
self.filter_apply();
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
self.toggle(self.layer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn arrow(&mut self, step: isize) -> bool {
|
|
||||||
let max = self.bindings.len().saturating_sub(1);
|
|
||||||
self.offset = self.offset.min(max);
|
|
||||||
self.cursor = self.cursor.min(max);
|
|
||||||
|
|
||||||
if step > 0 { self.next(step as usize) } else { self.prev(step.unsigned_abs()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn next(&mut self, step: usize) -> bool {
|
|
||||||
let len = self.bindings.len();
|
|
||||||
if len == 0 {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let old = self.cursor;
|
|
||||||
self.cursor = (self.cursor + step).min(len - 1);
|
|
||||||
|
|
||||||
let limit = Self::limit();
|
|
||||||
if self.cursor >= (self.offset + limit).min(len).saturating_sub(5) {
|
|
||||||
self.offset = len.saturating_sub(limit).min(self.offset + self.cursor - old);
|
|
||||||
}
|
|
||||||
|
|
||||||
old != self.cursor
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn prev(&mut self, step: usize) -> bool {
|
|
||||||
let old = self.cursor;
|
|
||||||
self.cursor = self.cursor.saturating_sub(step);
|
|
||||||
|
|
||||||
if self.cursor < self.offset + 5 {
|
|
||||||
self.offset = self.offset.saturating_sub(old - self.cursor);
|
|
||||||
}
|
|
||||||
|
|
||||||
old != self.cursor
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn filter(&mut self) -> bool {
|
|
||||||
self.in_filter = Some(Default::default());
|
|
||||||
self.filter_apply();
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn filter_apply(&mut self) -> bool {
|
|
||||||
let kw = self.in_filter.as_ref().map(|i| i.value()).filter(|v| !v.is_empty());
|
let kw = self.in_filter.as_ref().map(|i| i.value()).filter(|v| !v.is_empty());
|
||||||
if self.keyword.as_deref() == kw {
|
if self.keyword.as_deref() == kw {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
mod commands;
|
||||||
mod help;
|
mod help;
|
||||||
|
|
||||||
pub use help::*;
|
pub use help::*;
|
||||||
|
|
|
||||||
35
yazi-core/src/input/commands/backward.rs
Normal file
35
yazi-core/src/input/commands/backward.rs
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
use yazi_config::keymap::Exec;
|
||||||
|
use yazi_shared::CharKind;
|
||||||
|
|
||||||
|
use crate::input::Input;
|
||||||
|
|
||||||
|
pub struct Opt;
|
||||||
|
|
||||||
|
impl From<&Exec> for Opt {
|
||||||
|
fn from(_: &Exec) -> Self { Self }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Input {
|
||||||
|
pub fn backward(&mut self, _: impl Into<Opt>) -> bool {
|
||||||
|
let snap = self.snap();
|
||||||
|
if snap.cursor == 0 {
|
||||||
|
return self.move_(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let idx = snap.idx(snap.cursor).unwrap_or(snap.len());
|
||||||
|
let mut it = snap.value[..idx].chars().rev().enumerate();
|
||||||
|
let mut prev = CharKind::new(it.next().unwrap().1);
|
||||||
|
for (i, c) in it {
|
||||||
|
let c = CharKind::new(c);
|
||||||
|
if prev != CharKind::Space && prev != c {
|
||||||
|
return self.move_(-(i as isize));
|
||||||
|
}
|
||||||
|
prev = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
if prev != CharKind::Space {
|
||||||
|
return self.move_(-(snap.len() as isize));
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
34
yazi-core/src/input/commands/close.rs
Normal file
34
yazi-core/src/input/commands/close.rs
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
use yazi_config::keymap::{Exec, KeymapLayer};
|
||||||
|
use yazi_shared::InputError;
|
||||||
|
|
||||||
|
use crate::{emit, input::Input};
|
||||||
|
|
||||||
|
pub struct Opt {
|
||||||
|
submit: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Exec> for Opt {
|
||||||
|
fn from(e: &Exec) -> Self { Self { submit: e.named.contains_key("submit") } }
|
||||||
|
}
|
||||||
|
impl From<bool> for Opt {
|
||||||
|
fn from(submit: bool) -> Self { Self { submit } }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Input {
|
||||||
|
pub fn close(&mut self, opt: impl Into<Opt>) -> bool {
|
||||||
|
let opt = opt.into() as Opt;
|
||||||
|
|
||||||
|
if self.completion {
|
||||||
|
emit!(Call(Exec::call("close", vec![]).vec(), KeymapLayer::Completion));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(cb) = self.callback.take() {
|
||||||
|
let value = self.snap_mut().value.clone();
|
||||||
|
_ = cb.send(if opt.submit { Ok(value) } else { Err(InputError::Canceled(value)) });
|
||||||
|
}
|
||||||
|
|
||||||
|
self.ticket = self.ticket.wrapping_add(1);
|
||||||
|
self.visible = false;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -18,7 +18,7 @@ impl<'a> From<&'a Exec> for Opt<'a> {
|
||||||
|
|
||||||
impl Input {
|
impl Input {
|
||||||
pub fn complete<'a>(&mut self, opt: impl Into<Opt<'a>>) -> bool {
|
pub fn complete<'a>(&mut self, opt: impl Into<Opt<'a>>) -> bool {
|
||||||
let opt = opt.into();
|
let opt = opt.into() as Opt;
|
||||||
if self.ticket != opt.ticket {
|
if self.ticket != opt.ticket {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
35
yazi-core/src/input/commands/delete.rs
Normal file
35
yazi-core/src/input/commands/delete.rs
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
use yazi_config::keymap::Exec;
|
||||||
|
|
||||||
|
use crate::input::{op::InputOp, Input};
|
||||||
|
|
||||||
|
pub struct Opt {
|
||||||
|
cut: bool,
|
||||||
|
insert: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Exec> for Opt {
|
||||||
|
fn from(e: &Exec) -> Self {
|
||||||
|
Self { cut: e.named.contains_key("cut"), insert: e.named.contains_key("insert") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Input {
|
||||||
|
pub fn delete(&mut self, opt: impl Into<Opt>) -> bool {
|
||||||
|
let opt = opt.into() as Opt;
|
||||||
|
match self.snap().op {
|
||||||
|
InputOp::None => {
|
||||||
|
self.snap_mut().op = InputOp::Delete(opt.cut, opt.insert, self.snap().cursor);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
InputOp::Select(start) => {
|
||||||
|
self.snap_mut().op = InputOp::Delete(opt.cut, opt.insert, start);
|
||||||
|
return self.handle_op(self.snap().cursor, true).then(|| self.move_(0)).is_some();
|
||||||
|
}
|
||||||
|
InputOp::Delete(..) => {
|
||||||
|
self.snap_mut().op = InputOp::Delete(opt.cut, opt.insert, 0);
|
||||||
|
return self.move_(self.snap().len() as isize);
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
yazi-core/src/input/commands/escape.rs
Normal file
36
yazi-core/src/input/commands/escape.rs
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
use yazi_config::keymap::{Exec, KeymapLayer};
|
||||||
|
|
||||||
|
use crate::{emit, input::{op::InputOp, Input, InputMode}};
|
||||||
|
|
||||||
|
pub struct Opt;
|
||||||
|
|
||||||
|
impl From<&Exec> for Opt {
|
||||||
|
fn from(_: &Exec) -> Self { Self }
|
||||||
|
}
|
||||||
|
impl From<()> for Opt {
|
||||||
|
fn from(_: ()) -> Self { Self }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Input {
|
||||||
|
pub fn escape(&mut self, _: impl Into<Opt>) -> bool {
|
||||||
|
let snap = self.snap_mut();
|
||||||
|
match snap.mode {
|
||||||
|
InputMode::Normal if snap.op == InputOp::None => {
|
||||||
|
self.close(false);
|
||||||
|
}
|
||||||
|
InputMode::Normal => {
|
||||||
|
snap.op = InputOp::None;
|
||||||
|
}
|
||||||
|
InputMode::Insert => {
|
||||||
|
snap.mode = InputMode::Normal;
|
||||||
|
self.move_(-1);
|
||||||
|
|
||||||
|
if self.completion {
|
||||||
|
emit!(Call(Exec::call("close", vec![]).vec(), KeymapLayer::Completion));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.snaps.tag();
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
42
yazi-core/src/input/commands/forward.rs
Normal file
42
yazi-core/src/input/commands/forward.rs
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
use yazi_config::keymap::Exec;
|
||||||
|
use yazi_shared::CharKind;
|
||||||
|
|
||||||
|
use crate::input::{op::InputOp, Input};
|
||||||
|
|
||||||
|
pub struct Opt {
|
||||||
|
end_of_word: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Exec> for Opt {
|
||||||
|
fn from(e: &Exec) -> Self { Self { end_of_word: e.named.contains_key("end-of-word") } }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Input {
|
||||||
|
pub fn forward(&mut self, opt: impl Into<Opt>) -> bool {
|
||||||
|
let opt = opt.into() as Opt;
|
||||||
|
|
||||||
|
let snap = self.snap();
|
||||||
|
if snap.value.is_empty() {
|
||||||
|
return self.move_(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut it = snap.value.chars().skip(snap.cursor).enumerate();
|
||||||
|
let mut prev = CharKind::new(it.next().unwrap().1);
|
||||||
|
for (i, c) in it {
|
||||||
|
let c = CharKind::new(c);
|
||||||
|
let b = if opt.end_of_word {
|
||||||
|
prev != CharKind::Space && prev != c && i != 1
|
||||||
|
} else {
|
||||||
|
c != CharKind::Space && c != prev
|
||||||
|
};
|
||||||
|
if b && !matches!(snap.op, InputOp::None | InputOp::Select(_)) {
|
||||||
|
return self.move_(i as isize);
|
||||||
|
} else if b {
|
||||||
|
return self.move_(if opt.end_of_word { i - 1 } else { i } as isize);
|
||||||
|
}
|
||||||
|
prev = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.move_(snap.len() as isize)
|
||||||
|
}
|
||||||
|
}
|
||||||
29
yazi-core/src/input/commands/insert.rs
Normal file
29
yazi-core/src/input/commands/insert.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
use yazi_config::keymap::Exec;
|
||||||
|
|
||||||
|
use crate::input::Input;
|
||||||
|
|
||||||
|
pub struct Opt {
|
||||||
|
append: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Exec> for Opt {
|
||||||
|
fn from(e: &Exec) -> Self { Self { append: e.named.contains_key("append") } }
|
||||||
|
}
|
||||||
|
impl From<bool> for Opt {
|
||||||
|
fn from(append: bool) -> Self { Self { append } }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Input {
|
||||||
|
pub fn insert(&mut self, opt: impl Into<Opt>) -> bool {
|
||||||
|
if !self.snap_mut().insert() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let opt = opt.into() as Opt;
|
||||||
|
if opt.append {
|
||||||
|
self.move_(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1 +1,13 @@
|
||||||
|
mod backward;
|
||||||
|
mod close;
|
||||||
mod complete;
|
mod complete;
|
||||||
|
mod delete;
|
||||||
|
mod escape;
|
||||||
|
mod forward;
|
||||||
|
mod insert;
|
||||||
|
mod move_;
|
||||||
|
mod paste;
|
||||||
|
mod redo;
|
||||||
|
mod undo;
|
||||||
|
mod visual;
|
||||||
|
mod yank;
|
||||||
|
|
|
||||||
57
yazi-core/src/input/commands/move_.rs
Normal file
57
yazi-core/src/input/commands/move_.rs
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
use yazi_config::keymap::Exec;
|
||||||
|
|
||||||
|
use crate::input::{op::InputOp, snap::InputSnap, Input};
|
||||||
|
|
||||||
|
pub struct Opt {
|
||||||
|
step: isize,
|
||||||
|
in_operating: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Exec> for Opt {
|
||||||
|
fn from(e: &Exec) -> Self {
|
||||||
|
Self {
|
||||||
|
step: e.args.first().and_then(|s| s.parse().ok()).unwrap_or(0),
|
||||||
|
in_operating: e.named.contains_key("in-operating"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<isize> for Opt {
|
||||||
|
fn from(step: isize) -> Self { Self { step, in_operating: false } }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Input {
|
||||||
|
pub fn move_(&mut self, opt: impl Into<Opt>) -> bool {
|
||||||
|
let opt = opt.into() as Opt;
|
||||||
|
|
||||||
|
let snap = self.snap();
|
||||||
|
if opt.in_operating && snap.op == InputOp::None {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let b = self.handle_op(
|
||||||
|
if opt.step <= 0 {
|
||||||
|
snap.cursor.saturating_sub(opt.step.unsigned_abs())
|
||||||
|
} else {
|
||||||
|
snap.count().min(snap.cursor + opt.step as usize)
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
let snap = self.snap_mut();
|
||||||
|
if snap.cursor < snap.offset {
|
||||||
|
snap.offset = snap.cursor;
|
||||||
|
} else if snap.value.is_empty() {
|
||||||
|
snap.offset = 0;
|
||||||
|
} else {
|
||||||
|
let delta = snap.mode.delta();
|
||||||
|
let s = snap.slice(snap.offset..snap.cursor + delta);
|
||||||
|
if s.width() >= /*TODO: hardcode*/ 50 - 2 {
|
||||||
|
let s = s.chars().rev().collect::<String>();
|
||||||
|
snap.offset = snap.cursor - InputSnap::find_window(&s, 0).end.saturating_sub(delta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b
|
||||||
|
}
|
||||||
|
}
|
||||||
31
yazi-core/src/input/commands/paste.rs
Normal file
31
yazi-core/src/input/commands/paste.rs
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
use yazi_config::keymap::Exec;
|
||||||
|
|
||||||
|
use crate::{external, input::{op::InputOp, Input}};
|
||||||
|
|
||||||
|
pub struct Opt {
|
||||||
|
before: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Exec> for Opt {
|
||||||
|
fn from(e: &Exec) -> Self { Self { before: e.named.contains_key("before") } }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Input {
|
||||||
|
pub fn paste(&mut self, opt: impl Into<Opt>) -> bool {
|
||||||
|
if let Some(start) = self.snap().op.start() {
|
||||||
|
self.snap_mut().op = InputOp::Delete(false, false, start);
|
||||||
|
self.handle_op(self.snap().cursor, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
let s = futures::executor::block_on(external::clipboard_get()).unwrap_or_default();
|
||||||
|
if s.is_empty() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let opt = opt.into() as Opt;
|
||||||
|
self.insert(!opt.before);
|
||||||
|
self.type_str(&s.to_string_lossy());
|
||||||
|
self.escape(());
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
13
yazi-core/src/input/commands/redo.rs
Normal file
13
yazi-core/src/input/commands/redo.rs
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
use yazi_config::keymap::Exec;
|
||||||
|
|
||||||
|
use crate::input::Input;
|
||||||
|
|
||||||
|
pub struct Opt;
|
||||||
|
|
||||||
|
impl From<&Exec> for Opt {
|
||||||
|
fn from(_: &Exec) -> Self { Self }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Input {
|
||||||
|
pub fn redo(&mut self, _: impl Into<Opt>) -> bool { self.snaps.redo() }
|
||||||
|
}
|
||||||
21
yazi-core/src/input/commands/undo.rs
Normal file
21
yazi-core/src/input/commands/undo.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
use yazi_config::keymap::Exec;
|
||||||
|
|
||||||
|
use crate::input::{Input, InputMode};
|
||||||
|
|
||||||
|
pub struct Opt;
|
||||||
|
|
||||||
|
impl From<&Exec> for Opt {
|
||||||
|
fn from(_: &Exec) -> Self { Self }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Input {
|
||||||
|
pub fn undo(&mut self, _: impl Into<Opt>) -> bool {
|
||||||
|
if !self.snaps.undo() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if self.snap().mode == InputMode::Insert {
|
||||||
|
self.escape(());
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
14
yazi-core/src/input/commands/visual.rs
Normal file
14
yazi-core/src/input/commands/visual.rs
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
use yazi_config::keymap::Exec;
|
||||||
|
|
||||||
|
use crate::input::Input;
|
||||||
|
|
||||||
|
pub struct Opt;
|
||||||
|
|
||||||
|
impl From<&Exec> for Opt {
|
||||||
|
fn from(_: &Exec) -> Self { Self }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Input {
|
||||||
|
#[inline]
|
||||||
|
pub fn visual(&mut self, _: impl Into<Opt>) -> bool { self.snap_mut().visual() }
|
||||||
|
}
|
||||||
30
yazi-core/src/input/commands/yank.rs
Normal file
30
yazi-core/src/input/commands/yank.rs
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
use yazi_config::keymap::Exec;
|
||||||
|
|
||||||
|
use crate::input::{op::InputOp, Input};
|
||||||
|
|
||||||
|
pub struct Opt;
|
||||||
|
|
||||||
|
impl From<&Exec> for Opt {
|
||||||
|
fn from(_: &Exec) -> Self { Self }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Input {
|
||||||
|
pub fn yank(&mut self, _: impl Into<Opt>) -> bool {
|
||||||
|
match self.snap().op {
|
||||||
|
InputOp::None => {
|
||||||
|
self.snap_mut().op = InputOp::Yank(self.snap().cursor);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
InputOp::Select(start) => {
|
||||||
|
self.snap_mut().op = InputOp::Yank(start);
|
||||||
|
return self.handle_op(self.snap().cursor, true).then(|| self.move_(0)).is_some();
|
||||||
|
}
|
||||||
|
InputOp::Yank(_) => {
|
||||||
|
self.snap_mut().op = InputOp::Yank(0);
|
||||||
|
self.move_(self.snap().len() as isize);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,11 +3,11 @@ use std::ops::Range;
|
||||||
use crossterm::event::KeyCode;
|
use crossterm::event::KeyCode;
|
||||||
use tokio::sync::mpsc::UnboundedSender;
|
use tokio::sync::mpsc::UnboundedSender;
|
||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
use yazi_config::keymap::{Exec, Key, KeymapLayer};
|
use yazi_config::keymap::Key;
|
||||||
use yazi_shared::{CharKind, InputError};
|
use yazi_shared::InputError;
|
||||||
|
|
||||||
use super::{mode::InputMode, op::InputOp, InputOpt, InputSnap, InputSnaps};
|
use super::{mode::InputMode, op::InputOp, InputOpt, InputSnap, InputSnaps};
|
||||||
use crate::{emit, external, Position};
|
use crate::{external, Position};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Input {
|
pub struct Input {
|
||||||
|
|
@ -19,9 +19,9 @@ pub struct Input {
|
||||||
pub position: Position,
|
pub position: Position,
|
||||||
|
|
||||||
// Typing
|
// Typing
|
||||||
callback: Option<UnboundedSender<Result<String, InputError>>>,
|
pub(super) callback: Option<UnboundedSender<Result<String, InputError>>>,
|
||||||
realtime: bool,
|
realtime: bool,
|
||||||
completion: bool,
|
pub(super) completion: bool,
|
||||||
|
|
||||||
// Shell
|
// Shell
|
||||||
pub(super) highlight: bool,
|
pub(super) highlight: bool,
|
||||||
|
|
@ -45,153 +45,6 @@ impl Input {
|
||||||
self.highlight = opt.highlight;
|
self.highlight = opt.highlight;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn close(&mut self, submit: bool) -> bool {
|
|
||||||
if let Some(cb) = self.callback.take() {
|
|
||||||
let value = self.snap_mut().value.clone();
|
|
||||||
_ = cb.send(if submit { Ok(value) } else { Err(InputError::Canceled(value)) });
|
|
||||||
}
|
|
||||||
|
|
||||||
self.ticket = self.ticket.wrapping_add(1);
|
|
||||||
self.visible = false;
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn escape(&mut self) -> bool {
|
|
||||||
let snap = self.snap_mut();
|
|
||||||
match snap.mode {
|
|
||||||
InputMode::Normal if snap.op == InputOp::None => {
|
|
||||||
self.close(false);
|
|
||||||
}
|
|
||||||
InputMode::Normal => {
|
|
||||||
snap.op = InputOp::None;
|
|
||||||
}
|
|
||||||
InputMode::Insert => {
|
|
||||||
snap.mode = InputMode::Normal;
|
|
||||||
self.move_(-1);
|
|
||||||
|
|
||||||
if self.completion {
|
|
||||||
emit!(Call(Exec::call("close", vec![]).vec(), KeymapLayer::Completion));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.snaps.tag();
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn insert(&mut self, append: bool) -> bool {
|
|
||||||
if !self.snap_mut().insert() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if append {
|
|
||||||
self.move_(1);
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn visual(&mut self) -> bool { self.snap_mut().visual() }
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn undo(&mut self) -> bool {
|
|
||||||
if !self.snaps.undo() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if self.snap().mode == InputMode::Insert {
|
|
||||||
self.escape();
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn redo(&mut self) -> bool {
|
|
||||||
if !self.snaps.redo() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn move_(&mut self, step: isize) -> bool {
|
|
||||||
let snap = self.snap();
|
|
||||||
let b = self.handle_op(
|
|
||||||
if step <= 0 {
|
|
||||||
snap.cursor.saturating_sub(step.unsigned_abs())
|
|
||||||
} else {
|
|
||||||
snap.count().min(snap.cursor + step as usize)
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
let snap = self.snap_mut();
|
|
||||||
if snap.cursor < snap.offset {
|
|
||||||
snap.offset = snap.cursor;
|
|
||||||
} else if snap.value.is_empty() {
|
|
||||||
snap.offset = 0;
|
|
||||||
} else {
|
|
||||||
let delta = snap.mode.delta();
|
|
||||||
let s = snap.slice(snap.offset..snap.cursor + delta);
|
|
||||||
if s.width() >= /*TODO: hardcode*/ 50 - 2 {
|
|
||||||
let s = s.chars().rev().collect::<String>();
|
|
||||||
snap.offset = snap.cursor - InputSnap::find_window(&s, 0).end.saturating_sub(delta);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
b
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn move_in_operating(&mut self, step: isize) -> bool {
|
|
||||||
if self.snap_mut().op == InputOp::None { false } else { self.move_(step) }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn backward(&mut self) -> bool {
|
|
||||||
let snap = self.snap();
|
|
||||||
if snap.cursor == 0 {
|
|
||||||
return self.move_(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
let idx = snap.idx(snap.cursor).unwrap_or(snap.len());
|
|
||||||
let mut it = snap.value[..idx].chars().rev().enumerate();
|
|
||||||
let mut prev = CharKind::new(it.next().unwrap().1);
|
|
||||||
for (i, c) in it {
|
|
||||||
let c = CharKind::new(c);
|
|
||||||
if prev != CharKind::Space && prev != c {
|
|
||||||
return self.move_(-(i as isize));
|
|
||||||
}
|
|
||||||
prev = c;
|
|
||||||
}
|
|
||||||
|
|
||||||
if prev != CharKind::Space {
|
|
||||||
return self.move_(-(snap.len() as isize));
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn forward(&mut self, end: bool) -> bool {
|
|
||||||
let snap = self.snap();
|
|
||||||
if snap.value.is_empty() {
|
|
||||||
return self.move_(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut it = snap.value.chars().skip(snap.cursor).enumerate();
|
|
||||||
let mut prev = CharKind::new(it.next().unwrap().1);
|
|
||||||
for (i, c) in it {
|
|
||||||
let c = CharKind::new(c);
|
|
||||||
let b = if end {
|
|
||||||
prev != CharKind::Space && prev != c && i != 1
|
|
||||||
} else {
|
|
||||||
c != CharKind::Space && c != prev
|
|
||||||
};
|
|
||||||
if b && !matches!(snap.op, InputOp::None | InputOp::Select(_)) {
|
|
||||||
return self.move_(i as isize);
|
|
||||||
} else if b {
|
|
||||||
return self.move_(if end { i - 1 } else { i } as isize);
|
|
||||||
}
|
|
||||||
prev = c;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.move_(snap.len() as isize)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn type_(&mut self, key: &Key) -> bool {
|
pub fn type_(&mut self, key: &Key) -> bool {
|
||||||
if self.mode() != InputMode::Insert {
|
if self.mode() != InputMode::Insert {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -234,61 +87,7 @@ impl Input {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(&mut self, cut: bool, insert: bool) -> bool {
|
pub(super) fn handle_op(&mut self, cursor: usize, include: bool) -> bool {
|
||||||
match self.snap().op {
|
|
||||||
InputOp::None => {
|
|
||||||
self.snap_mut().op = InputOp::Delete(cut, insert, self.snap().cursor);
|
|
||||||
false
|
|
||||||
}
|
|
||||||
InputOp::Select(start) => {
|
|
||||||
self.snap_mut().op = InputOp::Delete(cut, insert, start);
|
|
||||||
return self.handle_op(self.snap().cursor, true).then(|| self.move_(0)).is_some();
|
|
||||||
}
|
|
||||||
InputOp::Delete(..) => {
|
|
||||||
self.snap_mut().op = InputOp::Delete(cut, insert, 0);
|
|
||||||
return self.move_(self.snap().len() as isize);
|
|
||||||
}
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn yank(&mut self) -> bool {
|
|
||||||
match self.snap().op {
|
|
||||||
InputOp::None => {
|
|
||||||
self.snap_mut().op = InputOp::Yank(self.snap().cursor);
|
|
||||||
false
|
|
||||||
}
|
|
||||||
InputOp::Select(start) => {
|
|
||||||
self.snap_mut().op = InputOp::Yank(start);
|
|
||||||
return self.handle_op(self.snap().cursor, true).then(|| self.move_(0)).is_some();
|
|
||||||
}
|
|
||||||
InputOp::Yank(_) => {
|
|
||||||
self.snap_mut().op = InputOp::Yank(0);
|
|
||||||
self.move_(self.snap().len() as isize);
|
|
||||||
false
|
|
||||||
}
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn paste(&mut self, before: bool) -> bool {
|
|
||||||
if let Some(start) = self.snap().op.start() {
|
|
||||||
self.snap_mut().op = InputOp::Delete(false, false, start);
|
|
||||||
self.handle_op(self.snap().cursor, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
let s = futures::executor::block_on(external::clipboard_get()).unwrap_or_default();
|
|
||||||
if s.is_empty() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.insert(!before);
|
|
||||||
self.type_str(&s.to_string_lossy());
|
|
||||||
self.escape();
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_op(&mut self, cursor: usize, include: bool) -> bool {
|
|
||||||
let old = self.snap().clone();
|
let old = self.snap().clone();
|
||||||
let snap = self.snaps.current_mut();
|
let snap = self.snaps.current_mut();
|
||||||
|
|
||||||
|
|
@ -380,8 +179,8 @@ impl Input {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn snap(&self) -> &InputSnap { self.snaps.current() }
|
pub(super) fn snap(&self) -> &InputSnap { self.snaps.current() }
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn snap_mut(&mut self) -> &mut InputSnap { self.snaps.current_mut() }
|
pub(super) fn snap_mut(&mut self) -> &mut InputSnap { self.snaps.current_mut() }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,18 @@
|
||||||
|
use yazi_config::keymap::Exec;
|
||||||
|
|
||||||
use crate::{manager::Manager, tasks::Tasks};
|
use crate::{manager::Manager, tasks::Tasks};
|
||||||
|
|
||||||
|
pub struct Opt;
|
||||||
|
|
||||||
|
impl From<&Exec> for Opt {
|
||||||
|
fn from(_: &Exec) -> Self { Self }
|
||||||
|
}
|
||||||
|
|
||||||
impl Manager {
|
impl Manager {
|
||||||
pub fn close(&mut self, tasks: &Tasks) -> bool {
|
pub fn close(&mut self, _: impl Into<Opt>, tasks: &Tasks) -> bool {
|
||||||
if self.tabs.len() > 1 {
|
if self.tabs.len() > 1 {
|
||||||
return self.tabs.close(self.tabs.idx);
|
return self.tabs.close(self.tabs.idx);
|
||||||
}
|
}
|
||||||
self.quit(tasks, false)
|
self.quit((), tasks)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,22 @@
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use tokio::fs::{self};
|
use tokio::fs::{self};
|
||||||
|
use yazi_config::keymap::Exec;
|
||||||
use yazi_shared::Url;
|
use yazi_shared::Url;
|
||||||
|
|
||||||
use crate::{emit, files::{File, FilesOp}, input::InputOpt, manager::Manager};
|
use crate::{emit, files::{File, FilesOp}, input::InputOpt, manager::Manager};
|
||||||
|
|
||||||
|
pub struct Opt {
|
||||||
|
force: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Exec> for Opt {
|
||||||
|
fn from(e: &Exec) -> Self { Self { force: e.named.contains_key("force") } }
|
||||||
|
}
|
||||||
|
|
||||||
impl Manager {
|
impl Manager {
|
||||||
pub fn create(&self, force: bool) -> bool {
|
pub fn create(&self, opt: impl Into<Opt>) -> bool {
|
||||||
|
let opt = opt.into() as Opt;
|
||||||
let cwd = self.cwd().to_owned();
|
let cwd = self.cwd().to_owned();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let mut result = emit!(Input(InputOpt::top("Create:")));
|
let mut result = emit!(Input(InputOpt::top("Create:")));
|
||||||
|
|
@ -15,7 +25,7 @@ impl Manager {
|
||||||
};
|
};
|
||||||
|
|
||||||
let path = cwd.join(&name);
|
let path = cwd.join(&name);
|
||||||
if !force && fs::symlink_metadata(&path).await.is_ok() {
|
if !opt.force && fs::symlink_metadata(&path).await.is_ok() {
|
||||||
match emit!(Input(InputOpt::top("Overwrite an existing file? (y/N)"))).recv().await {
|
match emit!(Input(InputOpt::top("Overwrite an existing file? (y/N)"))).recv().await {
|
||||||
Some(Ok(c)) if c == "y" || c == "Y" => (),
|
Some(Ok(c)) if c == "y" || c == "Y" => (),
|
||||||
_ => return Ok(()),
|
_ => return Ok(()),
|
||||||
|
|
|
||||||
22
yazi-core/src/manager/commands/link.rs
Normal file
22
yazi-core/src/manager/commands/link.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
use yazi_config::keymap::Exec;
|
||||||
|
|
||||||
|
use crate::{manager::Manager, tasks::Tasks};
|
||||||
|
|
||||||
|
pub struct Opt {
|
||||||
|
relative: bool,
|
||||||
|
force: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Exec> for Opt {
|
||||||
|
fn from(e: &Exec) -> Self {
|
||||||
|
Self { relative: e.named.contains_key("relative"), force: e.named.contains_key("force") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Manager {
|
||||||
|
pub fn link(&mut self, opt: impl Into<Opt>, tasks: &Tasks) -> bool {
|
||||||
|
let opt = opt.into() as Opt;
|
||||||
|
let (cut, ref src) = self.yanked;
|
||||||
|
!cut && tasks.file_link(src, self.cwd(), opt.relative, opt.force)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,16 @@
|
||||||
mod close;
|
mod close;
|
||||||
mod create;
|
mod create;
|
||||||
|
mod link;
|
||||||
mod open;
|
mod open;
|
||||||
|
mod paste;
|
||||||
mod peek;
|
mod peek;
|
||||||
mod quit;
|
mod quit;
|
||||||
mod refresh;
|
mod refresh;
|
||||||
|
mod remove;
|
||||||
mod rename;
|
mod rename;
|
||||||
mod suspend;
|
mod suspend;
|
||||||
|
mod tab_close;
|
||||||
|
mod tab_create;
|
||||||
|
mod tab_swap;
|
||||||
|
mod tab_switch;
|
||||||
mod yank;
|
mod yank;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,36 @@
|
||||||
use yazi_config::OPEN;
|
use std::ffi::OsString;
|
||||||
|
|
||||||
|
use yazi_config::{keymap::Exec, OPEN};
|
||||||
use yazi_shared::MIME_DIR;
|
use yazi_shared::MIME_DIR;
|
||||||
|
|
||||||
use crate::{emit, external, manager::Manager, select::SelectOpt};
|
use crate::{emit, external, manager::Manager, select::SelectOpt};
|
||||||
|
|
||||||
|
pub struct Opt {
|
||||||
|
interactive: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Exec> for Opt {
|
||||||
|
fn from(e: &Exec) -> Self { Self { interactive: e.named.contains_key("interactive") } }
|
||||||
|
}
|
||||||
|
|
||||||
impl Manager {
|
impl Manager {
|
||||||
pub fn open(&mut self, interactive: bool) -> bool {
|
async fn open_interactive(files: Vec<(OsString, String)>) {
|
||||||
|
let openers = OPEN.common_openers(&files);
|
||||||
|
if openers.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = emit!(Select(SelectOpt::hovered(
|
||||||
|
"Open with:",
|
||||||
|
openers.iter().map(|o| o.desc.clone()).collect()
|
||||||
|
)));
|
||||||
|
|
||||||
|
if let Ok(choice) = result.await {
|
||||||
|
emit!(Open(files, Some(openers[choice].clone())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open(&mut self, opt: impl Into<Opt>) -> bool {
|
||||||
let mut files: Vec<_> = self
|
let mut files: Vec<_> = self
|
||||||
.selected()
|
.selected()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|
@ -20,6 +46,7 @@ impl Manager {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let opt = opt.into() as Opt;
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let todo: Vec<_> = files.iter().filter(|(_, m)| m.is_none()).map(|(u, _)| u).collect();
|
let todo: Vec<_> = files.iter().filter(|(_, m)| m.is_none()).map(|(u, _)| u).collect();
|
||||||
if let Ok(mut mimes) = external::file(&todo).await {
|
if let Ok(mut mimes) = external::file(&todo).await {
|
||||||
|
|
@ -35,23 +62,12 @@ impl Manager {
|
||||||
let files: Vec<_> =
|
let files: Vec<_> =
|
||||||
files.into_iter().filter_map(|(u, m)| m.map(|m| (u.into_os_string(), m))).collect();
|
files.into_iter().filter_map(|(u, m)| m.map(|m| (u.into_os_string(), m))).collect();
|
||||||
|
|
||||||
if !interactive {
|
if opt.interactive {
|
||||||
emit!(Open(files, None));
|
Self::open_interactive(files).await;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let openers = OPEN.common_openers(&files);
|
emit!(Open(files, None));
|
||||||
if openers.is_empty() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = emit!(Select(SelectOpt::hovered(
|
|
||||||
"Open with:",
|
|
||||||
openers.iter().map(|o| o.desc.clone()).collect()
|
|
||||||
)));
|
|
||||||
if let Ok(choice) = result.await {
|
|
||||||
emit!(Open(files, Some(openers[choice].clone())));
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
21
yazi-core/src/manager/commands/paste.rs
Normal file
21
yazi-core/src/manager/commands/paste.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
use yazi_config::keymap::Exec;
|
||||||
|
|
||||||
|
use crate::{manager::Manager, tasks::Tasks};
|
||||||
|
|
||||||
|
pub struct Opt {
|
||||||
|
force: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Exec> for Opt {
|
||||||
|
fn from(e: &Exec) -> Self { Self { force: e.named.contains_key("force") } }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Manager {
|
||||||
|
pub fn paste(&mut self, opt: impl Into<Opt>, tasks: &Tasks) -> bool {
|
||||||
|
let dest = self.cwd();
|
||||||
|
let (cut, ref src) = self.yanked;
|
||||||
|
|
||||||
|
let opt = opt.into() as Opt;
|
||||||
|
if cut { tasks.file_cut(src, dest, opt.force) } else { tasks.file_copy(src, dest, opt.force) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,25 @@
|
||||||
|
use yazi_config::keymap::Exec;
|
||||||
|
|
||||||
use crate::{emit, input::InputOpt, manager::Manager, tasks::Tasks};
|
use crate::{emit, input::InputOpt, manager::Manager, tasks::Tasks};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Opt {
|
||||||
|
no_cwd_file: bool,
|
||||||
|
}
|
||||||
|
impl From<()> for Opt {
|
||||||
|
fn from(_: ()) -> Self { Self::default() }
|
||||||
|
}
|
||||||
|
impl From<&Exec> for Opt {
|
||||||
|
fn from(e: &Exec) -> Self { Self { no_cwd_file: e.named.contains_key("no-cwd-file") } }
|
||||||
|
}
|
||||||
|
|
||||||
impl Manager {
|
impl Manager {
|
||||||
pub fn quit(&self, tasks: &Tasks, no_cwd_file: bool) -> bool {
|
pub fn quit(&self, opt: impl Into<Opt>, tasks: &Tasks) -> bool {
|
||||||
|
let opt = opt.into() as Opt;
|
||||||
|
|
||||||
let tasks = tasks.len();
|
let tasks = tasks.len();
|
||||||
if tasks == 0 {
|
if tasks == 0 {
|
||||||
emit!(Quit(no_cwd_file));
|
emit!(Quit(opt.no_cwd_file));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -15,7 +30,7 @@ impl Manager {
|
||||||
|
|
||||||
if let Some(Ok(choice)) = result.recv().await {
|
if let Some(Ok(choice)) = result.recv().await {
|
||||||
if choice == "y" || choice == "Y" {
|
if choice == "y" || choice == "Y" {
|
||||||
emit!(Quit(no_cwd_file));
|
emit!(Quit(opt.no_cwd_file));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
25
yazi-core/src/manager/commands/remove.rs
Normal file
25
yazi-core/src/manager/commands/remove.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
use yazi_config::keymap::Exec;
|
||||||
|
|
||||||
|
use crate::{manager::Manager, tasks::Tasks};
|
||||||
|
|
||||||
|
pub struct Opt {
|
||||||
|
force: bool,
|
||||||
|
permanently: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Exec> for Opt {
|
||||||
|
fn from(e: &Exec) -> Self {
|
||||||
|
Self {
|
||||||
|
force: e.named.contains_key("force"),
|
||||||
|
permanently: e.named.contains_key("permanently"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Manager {
|
||||||
|
pub fn remove(&mut self, opt: impl Into<Opt>, tasks: &Tasks) -> bool {
|
||||||
|
let opt = opt.into() as Opt;
|
||||||
|
let targets = self.selected().into_iter().map(|f| f.url()).collect();
|
||||||
|
tasks.file_remove(targets, opt.force, opt.permanently)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,13 +2,36 @@ use std::{collections::BTreeSet, ffi::OsStr, io::{stdout, BufWriter, Write}, pat
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, Result};
|
use anyhow::{anyhow, bail, Result};
|
||||||
use tokio::{fs::{self, OpenOptions}, io::{stdin, AsyncReadExt, AsyncWriteExt}};
|
use tokio::{fs::{self, OpenOptions}, io::{stdin, AsyncReadExt, AsyncWriteExt}};
|
||||||
use yazi_config::{OPEN, PREVIEW};
|
use yazi_config::{keymap::Exec, OPEN, PREVIEW};
|
||||||
use yazi_shared::{max_common_root, Defer, Term, Url};
|
use yazi_shared::{max_common_root, Defer, Term, Url};
|
||||||
|
|
||||||
use crate::{emit, external::{self, ShellOpt}, files::{File, FilesOp}, input::InputOpt, manager::Manager, Event, BLOCKER};
|
use crate::{emit, external::{self, ShellOpt}, files::{File, FilesOp}, input::InputOpt, manager::Manager, Event, BLOCKER};
|
||||||
|
|
||||||
|
pub struct Opt {
|
||||||
|
force: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Exec> for Opt {
|
||||||
|
fn from(e: &Exec) -> Self { Self { force: e.named.contains_key("force") } }
|
||||||
|
}
|
||||||
|
|
||||||
impl Manager {
|
impl Manager {
|
||||||
pub fn rename(&self, force: bool) -> bool {
|
async fn rename_and_hover(old: Url, new: Url) -> Result<()> {
|
||||||
|
fs::rename(&old, &new).await?;
|
||||||
|
if old.parent() != new.parent() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let parent = old.parent_url().unwrap();
|
||||||
|
emit!(Files(FilesOp::Deleting(parent, BTreeSet::from([old]))));
|
||||||
|
|
||||||
|
let file = File::from(new.clone()).await?;
|
||||||
|
emit!(Files(FilesOp::Creating(file.parent().unwrap(), file.into_map())));
|
||||||
|
emit!(Hover(new));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rename(&self, opt: impl Into<Opt>) -> bool {
|
||||||
if self.active().in_selecting() {
|
if self.active().in_selecting() {
|
||||||
return self.bulk_rename();
|
return self.bulk_rename();
|
||||||
}
|
}
|
||||||
|
|
@ -17,21 +40,7 @@ impl Manager {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
async fn rename_and_hover(old: Url, new: Url) -> Result<()> {
|
let opt = opt.into() as Opt;
|
||||||
fs::rename(&old, &new).await?;
|
|
||||||
if old.parent() != new.parent() {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let parent = old.parent_url().unwrap();
|
|
||||||
emit!(Files(FilesOp::Deleting(parent, BTreeSet::from([old]))));
|
|
||||||
|
|
||||||
let file = File::from(new.clone()).await?;
|
|
||||||
emit!(Files(FilesOp::Creating(file.parent().unwrap(), file.into_map())));
|
|
||||||
emit!(Hover(new));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let mut result = emit!(Input(
|
let mut result = emit!(Input(
|
||||||
InputOpt::hovered("Rename:").with_value(hovered.file_name().unwrap().to_string_lossy())
|
InputOpt::hovered("Rename:").with_value(hovered.file_name().unwrap().to_string_lossy())
|
||||||
|
|
@ -42,15 +51,15 @@ impl Manager {
|
||||||
};
|
};
|
||||||
|
|
||||||
let new = hovered.parent().unwrap().join(name);
|
let new = hovered.parent().unwrap().join(name);
|
||||||
if force || fs::symlink_metadata(&new).await.is_err() {
|
if opt.force || fs::symlink_metadata(&new).await.is_err() {
|
||||||
rename_and_hover(hovered, Url::from(new)).await.ok();
|
Self::rename_and_hover(hovered, Url::from(new)).await.ok();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut result = emit!(Input(InputOpt::hovered("Overwrite an existing file? (y/N)")));
|
let mut result = emit!(Input(InputOpt::hovered("Overwrite an existing file? (y/N)")));
|
||||||
if let Some(Ok(choice)) = result.recv().await {
|
if let Some(Ok(choice)) = result.recv().await {
|
||||||
if choice == "y" || choice == "Y" {
|
if choice == "y" || choice == "Y" {
|
||||||
rename_and_hover(hovered, Url::from(new)).await.ok();
|
Self::rename_and_hover(hovered, Url::from(new)).await.ok();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,14 @@
|
||||||
|
use yazi_config::keymap::Exec;
|
||||||
|
|
||||||
use crate::manager::Manager;
|
use crate::manager::Manager;
|
||||||
|
|
||||||
|
pub struct Opt;
|
||||||
|
impl From<&Exec> for Opt {
|
||||||
|
fn from(_: &Exec) -> Self { Self }
|
||||||
|
}
|
||||||
|
|
||||||
impl Manager {
|
impl Manager {
|
||||||
pub fn suspend(&mut self) -> bool {
|
pub fn suspend(&mut self, _: impl Into<Opt>) -> bool {
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
crate::emit!(Stop(true)).await;
|
crate::emit!(Stop(true)).await;
|
||||||
|
|
|
||||||
35
yazi-core/src/manager/commands/tab_close.rs
Normal file
35
yazi-core/src/manager/commands/tab_close.rs
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
use yazi_config::keymap::Exec;
|
||||||
|
|
||||||
|
use crate::manager::Tabs;
|
||||||
|
|
||||||
|
pub struct Opt {
|
||||||
|
idx: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Exec> for Opt {
|
||||||
|
fn from(e: &Exec) -> Self {
|
||||||
|
Self { idx: e.args.first().and_then(|i| i.parse().ok()).unwrap_or(0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<usize> for Opt {
|
||||||
|
fn from(idx: usize) -> Self { Self { idx } }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tabs {
|
||||||
|
pub fn close(&mut self, opt: impl Into<Opt>) -> bool {
|
||||||
|
let opt = opt.into() as Opt;
|
||||||
|
|
||||||
|
let len = self.items.len();
|
||||||
|
if len < 2 || opt.idx >= len {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.items.remove(opt.idx);
|
||||||
|
if opt.idx <= self.idx {
|
||||||
|
self.set_idx(self.absolute(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
41
yazi-core/src/manager/commands/tab_create.rs
Normal file
41
yazi-core/src/manager/commands/tab_create.rs
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
use yazi_config::keymap::Exec;
|
||||||
|
use yazi_shared::Url;
|
||||||
|
|
||||||
|
use crate::{manager::Tabs, tab::Tab};
|
||||||
|
|
||||||
|
const MAX_TABS: usize = 9;
|
||||||
|
|
||||||
|
pub struct Opt {
|
||||||
|
url: Option<Url>,
|
||||||
|
current: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Exec> for Opt {
|
||||||
|
fn from(e: &Exec) -> Self {
|
||||||
|
let mut opt = Self { url: None, current: e.named.contains_key("current") };
|
||||||
|
|
||||||
|
if !opt.current {
|
||||||
|
opt.url = Some(e.args.first().map_or_else(|| Url::from("."), Url::from));
|
||||||
|
}
|
||||||
|
opt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tabs {
|
||||||
|
pub fn create(&mut self, opt: impl Into<Opt>) -> bool {
|
||||||
|
if self.items.len() >= MAX_TABS {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let opt = opt.into() as Opt;
|
||||||
|
let url = if opt.current { self.active().current.cwd.to_owned() } else { opt.url.unwrap() };
|
||||||
|
|
||||||
|
let mut tab = Tab::from(url);
|
||||||
|
tab.conf = self.active().conf.clone();
|
||||||
|
tab.apply_files_attrs(false);
|
||||||
|
|
||||||
|
self.items.insert(self.idx + 1, tab);
|
||||||
|
self.set_idx(self.idx + 1);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
26
yazi-core/src/manager/commands/tab_swap.rs
Normal file
26
yazi-core/src/manager/commands/tab_swap.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
use yazi_config::keymap::Exec;
|
||||||
|
|
||||||
|
use crate::manager::Tabs;
|
||||||
|
|
||||||
|
pub struct Opt {
|
||||||
|
step: isize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Exec> for Opt {
|
||||||
|
fn from(e: &Exec) -> Self {
|
||||||
|
Self { step: e.args.first().and_then(|s| s.parse().ok()).unwrap_or(0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tabs {
|
||||||
|
pub fn swap(&mut self, opt: impl Into<Opt>) -> bool {
|
||||||
|
let idx = self.absolute(opt.into().step);
|
||||||
|
if idx == self.idx {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.items.swap(self.idx, idx);
|
||||||
|
self.set_idx(idx);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
35
yazi-core/src/manager/commands/tab_switch.rs
Normal file
35
yazi-core/src/manager/commands/tab_switch.rs
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
use yazi_config::keymap::Exec;
|
||||||
|
|
||||||
|
use crate::manager::Tabs;
|
||||||
|
|
||||||
|
pub struct Opt {
|
||||||
|
step: isize,
|
||||||
|
relative: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Exec> for Opt {
|
||||||
|
fn from(e: &Exec) -> Self {
|
||||||
|
Self {
|
||||||
|
step: e.args.first().and_then(|s| s.parse().ok()).unwrap_or(0),
|
||||||
|
relative: e.named.contains_key("relative"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tabs {
|
||||||
|
pub fn switch(&mut self, opt: impl Into<Opt>) -> bool {
|
||||||
|
let opt = opt.into() as Opt;
|
||||||
|
let idx = if opt.relative {
|
||||||
|
(self.idx as isize + opt.step).rem_euclid(self.items.len() as isize) as usize
|
||||||
|
} else {
|
||||||
|
opt.step as usize
|
||||||
|
};
|
||||||
|
|
||||||
|
if idx == self.idx || idx >= self.items.len() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.set_idx(idx);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,20 @@
|
||||||
|
use yazi_config::keymap::Exec;
|
||||||
|
|
||||||
use crate::manager::Manager;
|
use crate::manager::Manager;
|
||||||
|
|
||||||
|
pub struct Opt {
|
||||||
|
cut: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Exec> for Opt {
|
||||||
|
fn from(e: &Exec) -> Self { Self { cut: e.named.contains_key("cut") } }
|
||||||
|
}
|
||||||
|
|
||||||
impl Manager {
|
impl Manager {
|
||||||
pub fn yank(&mut self, cut: bool) -> bool {
|
pub fn yank(&mut self, opt: impl Into<Opt>) -> bool {
|
||||||
self.yanked.0 = cut;
|
let opt = opt.into() as Opt;
|
||||||
|
|
||||||
|
self.yanked.0 = opt.cut;
|
||||||
self.yanked.1 = self.selected().into_iter().map(|f| f.url()).collect();
|
self.yanked.1 = self.selected().into_iter().map(|f| f.url()).collect();
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ impl Manager {
|
||||||
|
|
||||||
if url == self.cwd() {
|
if url == self.cwd() {
|
||||||
self.current_mut().update(op);
|
self.current_mut().update(op);
|
||||||
self.active_mut().leave();
|
self.active_mut().leave(());
|
||||||
true
|
true
|
||||||
} else if matches!(self.parent(), Some(p) if &p.cwd == url) {
|
} else if matches!(self.parent(), Some(p) if &p.cwd == url) {
|
||||||
self.active_mut().parent.as_mut().unwrap().update(op)
|
self.active_mut().parent.as_mut().unwrap().update(op)
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,9 @@ use yazi_shared::Url;
|
||||||
|
|
||||||
use crate::{emit, tab::Tab};
|
use crate::{emit, tab::Tab};
|
||||||
|
|
||||||
const MAX_TABS: usize = 9;
|
|
||||||
|
|
||||||
pub struct Tabs {
|
pub struct Tabs {
|
||||||
pub idx: usize,
|
pub idx: usize,
|
||||||
items: Vec<Tab>,
|
pub(super) items: Vec<Tab>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tabs {
|
impl Tabs {
|
||||||
|
|
@ -17,62 +15,8 @@ impl Tabs {
|
||||||
tabs
|
tabs
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create(&mut self, url: &Url) -> bool {
|
|
||||||
if self.items.len() >= MAX_TABS {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut tab = Tab::from(url);
|
|
||||||
tab.conf = self.active().conf.clone();
|
|
||||||
tab.apply_files_attrs(false);
|
|
||||||
|
|
||||||
self.items.insert(self.idx + 1, tab);
|
|
||||||
self.set_idx(self.idx + 1);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn switch(&mut self, idx: isize, rel: bool) -> bool {
|
|
||||||
let idx = if rel {
|
|
||||||
(self.idx as isize + idx).rem_euclid(self.items.len() as isize) as usize
|
|
||||||
} else {
|
|
||||||
idx as usize
|
|
||||||
};
|
|
||||||
|
|
||||||
if idx == self.idx || idx >= self.items.len() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.set_idx(idx);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn swap(&mut self, rel: isize) -> bool {
|
|
||||||
let idx = self.absolute(rel);
|
|
||||||
if idx == self.idx {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.items.swap(self.idx, idx);
|
|
||||||
self.set_idx(idx);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn close(&mut self, idx: usize) -> bool {
|
|
||||||
let len = self.items.len();
|
|
||||||
if len < 2 || idx >= len {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.items.remove(idx);
|
|
||||||
if idx <= self.idx {
|
|
||||||
self.set_idx(self.absolute(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn absolute(&self, rel: isize) -> usize {
|
pub(super) fn absolute(&self, rel: isize) -> usize {
|
||||||
if rel > 0 {
|
if rel > 0 {
|
||||||
(self.idx + rel as usize).min(self.items.len() - 1)
|
(self.idx + rel as usize).min(self.items.len() - 1)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -81,7 +25,7 @@ impl Tabs {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn set_idx(&mut self, idx: usize) {
|
pub(super) fn set_idx(&mut self, idx: usize) {
|
||||||
self.idx = idx;
|
self.idx = idx;
|
||||||
self.active_mut().preview.reset(|l| l.is_image());
|
self.active_mut().preview.reset(|l| l.is_image());
|
||||||
emit!(Refresh);
|
emit!(Refresh);
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ impl Watcher {
|
||||||
);
|
);
|
||||||
|
|
||||||
let instance = Self { watcher: watcher.unwrap(), watched: Default::default() };
|
let instance = Self { watcher: watcher.unwrap(), watched: Default::default() };
|
||||||
tokio::spawn(Self::changed(rx, instance.watched.clone()));
|
tokio::spawn(Self::on_changed(rx, instance.watched.clone()));
|
||||||
instance
|
instance
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -124,13 +124,17 @@ impl Watcher {
|
||||||
|
|
||||||
let watched = self.watched.clone();
|
let watched = self.watched.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
|
let watched = watched.read().clone();
|
||||||
for dir in dirs {
|
for dir in dirs {
|
||||||
Self::dir_changed(&dir, watched.clone()).await;
|
Self::dir_changed(&dir, &watched).await;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn changed(rx: UnboundedReceiver<Url>, watched: Arc<RwLock<IndexMap<Url, Option<Url>>>>) {
|
async fn on_changed(
|
||||||
|
rx: UnboundedReceiver<Url>,
|
||||||
|
watched: Arc<RwLock<IndexMap<Url, Option<Url>>>>,
|
||||||
|
) {
|
||||||
// TODO: revert this once a new notification is implemented
|
// TODO: revert this once a new notification is implemented
|
||||||
// let rx = UnboundedReceiverStream::new(rx).chunks_timeout(100,
|
// let rx = UnboundedReceiverStream::new(rx).chunks_timeout(100,
|
||||||
// Duration::from_millis(200));
|
// Duration::from_millis(200));
|
||||||
|
|
@ -147,70 +151,76 @@ impl Watcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Self::file_changed(&files, watched.clone()).await;
|
let watched = watched.read().clone();
|
||||||
|
|
||||||
|
Self::files_changed(&files, &watched).await;
|
||||||
for file in files {
|
for file in files {
|
||||||
|
for u in Self::linked_urls(&file, &watched) {
|
||||||
|
emit!(Files(FilesOp::IOErr(u.clone())));
|
||||||
|
}
|
||||||
emit!(Files(FilesOp::IOErr(file)));
|
emit!(Files(FilesOp::IOErr(file)));
|
||||||
}
|
}
|
||||||
|
|
||||||
for dir in dirs {
|
for dir in dirs {
|
||||||
Self::dir_changed(&dir, watched.clone()).await;
|
Self::dir_changed(&dir, &watched).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn file_changed(urls: &[Url], watched: Arc<RwLock<IndexMap<Url, Option<Url>>>>) {
|
async fn files_changed(urls: &[Url], watched: &IndexMap<Url, Option<Url>>) {
|
||||||
let Ok(mut mimes) = external::file(urls).await else {
|
let Ok(mut mimes) = external::file(urls).await else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let linked: Vec<_> = watched
|
let linked: Vec<_> = watched.iter().filter_map(|(k, v)| v.as_ref().map(|v| (k, v))).fold(
|
||||||
.read()
|
Vec::new(),
|
||||||
.iter()
|
|mut aac, (k, v)| {
|
||||||
.filter_map(|(k, v)| v.as_ref().map(|v| (k, v)))
|
|
||||||
.fold(Vec::new(), |mut aac, (k, v)| {
|
|
||||||
mimes
|
mimes
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(f, _)| f.parent().map(|p| p == **v) == Some(true))
|
.filter(|(u, _)| u.parent().map(|p| p == **v) == Some(true))
|
||||||
.for_each(|(f, m)| aac.push((k.join(f.file_name().unwrap()), m.clone())));
|
.for_each(|(u, m)| aac.push((k.join(u.file_name().unwrap()), m.clone())));
|
||||||
aac
|
aac
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
mimes.extend(linked);
|
mimes.extend(linked);
|
||||||
emit!(Mimetype(mimes));
|
emit!(Mimetype(mimes));
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn dir_changed(url: &Url, watched: Arc<RwLock<IndexMap<Url, Option<Url>>>>) {
|
async fn dir_changed(url: &Url, watched: &IndexMap<Url, Option<Url>>) {
|
||||||
let linked: Vec<_> = watched
|
let linked = Self::linked_urls(url, watched);
|
||||||
.read()
|
|
||||||
.iter()
|
|
||||||
.filter_map(|(k, v)| v.as_ref().map(|v| (k, v)))
|
|
||||||
.filter(|(_, v)| *v == url)
|
|
||||||
.map(|(k, _)| k.clone())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let Ok(rx) = Files::from_dir(url).await else {
|
let Ok(rx) = Files::from_dir(url).await else {
|
||||||
emit!(Files(FilesOp::IOErr(url.clone())));
|
emit!(Files(FilesOp::IOErr(url.clone())));
|
||||||
for ori in linked {
|
for u in linked {
|
||||||
emit!(Files(FilesOp::IOErr(ori)));
|
emit!(Files(FilesOp::IOErr(u.clone())));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let linked_files = |files: &[File], ori: &Url| -> Vec<File> {
|
let linked_files = |files: &[File], linked: &Url| -> Vec<File> {
|
||||||
let mut new = Vec::with_capacity(files.len());
|
let mut new = Vec::with_capacity(files.len());
|
||||||
for file in files {
|
for file in files {
|
||||||
let mut file = file.clone();
|
let mut file = file.clone();
|
||||||
file.url = ori.join(file.url.strip_prefix(url).unwrap());
|
file.url = linked.join(file.url.strip_prefix(url).unwrap());
|
||||||
new.push(file);
|
new.push(file);
|
||||||
}
|
}
|
||||||
new
|
new
|
||||||
};
|
};
|
||||||
|
|
||||||
let files: Vec<_> = UnboundedReceiverStream::new(rx).collect().await;
|
let files: Vec<_> = UnboundedReceiverStream::new(rx).collect().await;
|
||||||
for ori in linked {
|
for u in linked {
|
||||||
let files = linked_files(&files, &ori);
|
let files = linked_files(&files, u);
|
||||||
emit!(Files(FilesOp::Full(ori, files)));
|
emit!(Files(FilesOp::Full(u.clone(), files)));
|
||||||
}
|
}
|
||||||
emit!(Files(FilesOp::Full(url.clone(), files)));
|
emit!(Files(FilesOp::Full(url.clone(), files)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn linked_urls<'a>(url: &'a Url, watched: &'a IndexMap<Url, Option<Url>>) -> Vec<&'a Url> {
|
||||||
|
watched
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(k, v)| v.as_ref().map(|v| (k, v)))
|
||||||
|
.filter(|(_, v)| *v == url)
|
||||||
|
.map(|(k, _)| k)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
48
yazi-core/src/select/commands/arrow.rs
Normal file
48
yazi-core/src/select/commands/arrow.rs
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
use yazi_config::keymap::Exec;
|
||||||
|
|
||||||
|
use crate::select::Select;
|
||||||
|
|
||||||
|
pub struct Opt {
|
||||||
|
step: isize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Exec> for Opt {
|
||||||
|
fn from(e: &Exec) -> Self {
|
||||||
|
Self { step: e.args.first().and_then(|s| s.parse().ok()).unwrap_or(0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Select {
|
||||||
|
fn next(&mut self, step: usize) -> bool {
|
||||||
|
let len = self.items.len();
|
||||||
|
if len == 0 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let old = self.cursor;
|
||||||
|
self.cursor = (self.cursor + step).min(len - 1);
|
||||||
|
|
||||||
|
let limit = self.limit();
|
||||||
|
if self.cursor >= len.min(self.offset + limit) {
|
||||||
|
self.offset = len.saturating_sub(limit).min(self.offset + self.cursor - old);
|
||||||
|
}
|
||||||
|
|
||||||
|
old != self.cursor
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prev(&mut self, step: usize) -> bool {
|
||||||
|
let old = self.cursor;
|
||||||
|
self.cursor = self.cursor.saturating_sub(step);
|
||||||
|
|
||||||
|
if self.cursor < self.offset {
|
||||||
|
self.offset = self.offset.saturating_sub(old - self.cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
old != self.cursor
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn arrow(&mut self, opt: impl Into<Opt>) -> bool {
|
||||||
|
let opt = opt.into() as Opt;
|
||||||
|
if opt.step > 0 { self.next(opt.step as usize) } else { self.prev(opt.step.unsigned_abs()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
29
yazi-core/src/select/commands/close.rs
Normal file
29
yazi-core/src/select/commands/close.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
use anyhow::anyhow;
|
||||||
|
use yazi_config::keymap::Exec;
|
||||||
|
|
||||||
|
use crate::select::Select;
|
||||||
|
|
||||||
|
pub struct Opt {
|
||||||
|
submit: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Exec> for Opt {
|
||||||
|
fn from(e: &Exec) -> Self { Self { submit: e.named.contains_key("submit") } }
|
||||||
|
}
|
||||||
|
impl From<bool> for Opt {
|
||||||
|
fn from(submit: bool) -> Self { Self { submit } }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Select {
|
||||||
|
pub fn close(&mut self, opt: impl Into<Opt>) -> bool {
|
||||||
|
let opt = opt.into() as Opt;
|
||||||
|
if let Some(cb) = self.callback.take() {
|
||||||
|
_ = cb.send(if opt.submit { Ok(self.cursor) } else { Err(anyhow!("canceled")) });
|
||||||
|
}
|
||||||
|
|
||||||
|
self.cursor = 0;
|
||||||
|
self.offset = 0;
|
||||||
|
self.visible = false;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
2
yazi-core/src/select/commands/mod.rs
Normal file
2
yazi-core/src/select/commands/mod.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
mod arrow;
|
||||||
|
mod close;
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
mod commands;
|
||||||
mod option;
|
mod option;
|
||||||
mod select;
|
mod select;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::Result;
|
||||||
use tokio::sync::oneshot::Sender;
|
use tokio::sync::oneshot::Sender;
|
||||||
|
|
||||||
use super::SelectOpt;
|
use super::SelectOpt;
|
||||||
|
|
@ -6,13 +6,13 @@ use crate::Position;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Select {
|
pub struct Select {
|
||||||
title: String,
|
title: String,
|
||||||
items: Vec<String>,
|
pub(super) items: Vec<String>,
|
||||||
pub position: Position,
|
pub position: Position,
|
||||||
|
|
||||||
offset: usize,
|
pub(super) offset: usize,
|
||||||
cursor: usize,
|
pub(super) cursor: usize,
|
||||||
callback: Option<Sender<Result<usize>>>,
|
pub(super) callback: Option<Sender<Result<usize>>>,
|
||||||
|
|
||||||
pub visible: bool,
|
pub visible: bool,
|
||||||
}
|
}
|
||||||
|
|
@ -29,45 +29,6 @@ impl Select {
|
||||||
self.visible = true;
|
self.visible = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn close(&mut self, submit: bool) -> bool {
|
|
||||||
if let Some(cb) = self.callback.take() {
|
|
||||||
_ = cb.send(if submit { Ok(self.cursor) } else { Err(anyhow!("canceled")) });
|
|
||||||
}
|
|
||||||
|
|
||||||
self.cursor = 0;
|
|
||||||
self.offset = 0;
|
|
||||||
self.visible = false;
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn next(&mut self, step: usize) -> bool {
|
|
||||||
let len = self.items.len();
|
|
||||||
if len == 0 {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let old = self.cursor;
|
|
||||||
self.cursor = (self.cursor + step).min(len - 1);
|
|
||||||
|
|
||||||
let limit = self.limit();
|
|
||||||
if self.cursor >= len.min(self.offset + limit) {
|
|
||||||
self.offset = len.saturating_sub(limit).min(self.offset + self.cursor - old);
|
|
||||||
}
|
|
||||||
|
|
||||||
old != self.cursor
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn prev(&mut self, step: usize) -> bool {
|
|
||||||
let old = self.cursor;
|
|
||||||
self.cursor = self.cursor.saturating_sub(step);
|
|
||||||
|
|
||||||
if self.cursor < self.offset {
|
|
||||||
self.offset = self.offset.saturating_sub(old - self.cursor);
|
|
||||||
}
|
|
||||||
|
|
||||||
old != self.cursor
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn window(&self) -> &[String] {
|
pub fn window(&self) -> &[String] {
|
||||||
let end = (self.offset + self.limit()).min(self.items.len());
|
let end = (self.offset + self.limit()).min(self.items.len());
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,32 @@
|
||||||
|
use yazi_config::keymap::Exec;
|
||||||
|
|
||||||
use crate::{emit, tab::Tab, Step};
|
use crate::{emit, tab::Tab, Step};
|
||||||
|
|
||||||
|
pub struct Opt {
|
||||||
|
step: Step,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Exec> for Opt {
|
||||||
|
fn from(e: &Exec) -> Self {
|
||||||
|
Self { step: e.args.first().and_then(|s| s.parse().ok()).unwrap_or_default() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> From<T> for Opt
|
||||||
|
where
|
||||||
|
T: Into<Step>,
|
||||||
|
{
|
||||||
|
fn from(t: T) -> Self { Self { step: t.into() } }
|
||||||
|
}
|
||||||
|
|
||||||
impl Tab {
|
impl Tab {
|
||||||
pub fn arrow(&mut self, step: Step) -> bool {
|
pub fn arrow(&mut self, opt: impl Into<Opt>) -> bool {
|
||||||
let ok = if step.is_positive() { self.current.next(step) } else { self.current.prev(step) };
|
let opt = opt.into() as Opt;
|
||||||
|
let ok = if opt.step.is_positive() {
|
||||||
|
self.current.next(opt.step)
|
||||||
|
} else {
|
||||||
|
self.current.prev(opt.step)
|
||||||
|
};
|
||||||
if !ok {
|
if !ok {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,26 @@
|
||||||
|
use yazi_config::keymap::Exec;
|
||||||
|
|
||||||
use crate::tab::Tab;
|
use crate::tab::Tab;
|
||||||
|
|
||||||
|
pub struct Opt;
|
||||||
|
impl From<()> for Opt {
|
||||||
|
fn from(_: ()) -> Self { Self }
|
||||||
|
}
|
||||||
|
impl From<&Exec> for Opt {
|
||||||
|
fn from(_: &Exec) -> Self { Self }
|
||||||
|
}
|
||||||
|
|
||||||
impl Tab {
|
impl Tab {
|
||||||
pub fn back(&mut self) -> bool {
|
pub fn back(&mut self, _: impl Into<Opt>) -> bool {
|
||||||
if let Some(url) = self.backstack.shift_backward().cloned() {
|
if let Some(url) = self.backstack.shift_backward().cloned() {
|
||||||
futures::executor::block_on(self.cd(url));
|
self.cd(url);
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn forward(&mut self) -> bool {
|
pub fn forward(&mut self, _: impl Into<Opt>) -> bool {
|
||||||
if let Some(url) = self.backstack.shift_forward().cloned() {
|
if let Some(url) = self.backstack.shift_forward().cloned() {
|
||||||
futures::executor::block_on(self.cd(url));
|
self.cd(url);
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,37 @@
|
||||||
use std::{mem, time::Duration};
|
use std::{mem, time::Duration};
|
||||||
|
|
||||||
use tokio::pin;
|
use tokio::{fs, pin};
|
||||||
use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt};
|
use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt};
|
||||||
use yazi_config::keymap::{Exec, KeymapLayer};
|
use yazi_config::keymap::{Exec, KeymapLayer};
|
||||||
use yazi_shared::{Debounce, InputError, Url};
|
use yazi_shared::{expand_path, Debounce, InputError, Url};
|
||||||
|
|
||||||
use crate::{emit, files::{File, FilesOp}, input::InputOpt, tab::Tab};
|
use crate::{emit, input::InputOpt, tab::Tab};
|
||||||
|
|
||||||
|
pub struct Opt {
|
||||||
|
target: Url,
|
||||||
|
interactive: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Exec> for Opt {
|
||||||
|
fn from(e: &Exec) -> Self {
|
||||||
|
Self {
|
||||||
|
target: e.args.first().map(Url::from).unwrap_or_default(),
|
||||||
|
interactive: e.named.contains_key("interactive"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<Url> for Opt {
|
||||||
|
fn from(target: Url) -> Self { Self { target, interactive: false } }
|
||||||
|
}
|
||||||
|
|
||||||
impl Tab {
|
impl Tab {
|
||||||
// TODO: change to sync, and remove `Event::Cd`
|
pub fn cd(&mut self, opt: impl Into<Opt>) -> bool {
|
||||||
pub async fn cd(&mut self, mut target: Url) -> bool {
|
let opt = opt.into() as Opt;
|
||||||
let Ok(file) = File::from(target.clone()).await else {
|
if opt.interactive {
|
||||||
return false;
|
return self.cd_interactive(opt);
|
||||||
};
|
|
||||||
|
|
||||||
let mut hovered = None;
|
|
||||||
if !file.is_dir() {
|
|
||||||
hovered = Some(file.url());
|
|
||||||
target = target.parent_url().unwrap();
|
|
||||||
emit!(Files(FilesOp::Creating(target.clone(), file.into_map())));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Already in target
|
if self.current.cwd == opt.target {
|
||||||
if self.current.cwd == target {
|
|
||||||
if let Some(h) = hovered {
|
|
||||||
emit!(Hover(h));
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -35,35 +41,34 @@ impl Tab {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Current
|
// Current
|
||||||
let rep = self.history_new(&target);
|
let rep = self.history_new(&opt.target);
|
||||||
let rep = mem::replace(&mut self.current, rep);
|
let rep = mem::replace(&mut self.current, rep);
|
||||||
if rep.cwd.is_regular() {
|
if rep.cwd.is_regular() {
|
||||||
self.history.insert(rep.cwd.clone(), rep);
|
self.history.insert(rep.cwd.clone(), rep);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parent
|
// Parent
|
||||||
if let Some(parent) = target.parent_url() {
|
if let Some(parent) = opt.target.parent_url() {
|
||||||
self.parent = Some(self.history_new(&parent));
|
self.parent = Some(self.history_new(&parent));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hover the file
|
|
||||||
if let Some(h) = hovered {
|
|
||||||
emit!(Hover(h));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Backstack
|
// Backstack
|
||||||
if target.is_regular() {
|
if opt.target.is_regular() {
|
||||||
self.backstack.push(target.clone());
|
self.backstack.push(opt.target.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
emit!(Refresh);
|
emit!(Refresh);
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cd_interactive(&mut self, target: Url) -> bool {
|
fn cd_interactive(&mut self, opt: impl Into<Opt>) -> bool {
|
||||||
|
let opt = opt.into() as Opt;
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let rx = emit!(Input(
|
let rx = emit!(Input(
|
||||||
InputOpt::top("Change directory:").with_value(target.to_string_lossy()).with_completion()
|
InputOpt::top("Change directory:")
|
||||||
|
.with_value(opt.target.to_string_lossy())
|
||||||
|
.with_completion()
|
||||||
));
|
));
|
||||||
|
|
||||||
let rx = Debounce::new(UnboundedReceiverStream::new(rx), Duration::from_millis(50));
|
let rx = Debounce::new(UnboundedReceiverStream::new(rx), Duration::from_millis(50));
|
||||||
|
|
@ -72,7 +77,18 @@ impl Tab {
|
||||||
while let Some(result) = rx.next().await {
|
while let Some(result) = rx.next().await {
|
||||||
match result {
|
match result {
|
||||||
Ok(s) => {
|
Ok(s) => {
|
||||||
emit!(Cd(Url::from(s.trim())));
|
let p = expand_path(s);
|
||||||
|
let Ok(meta) = fs::metadata(&p).await else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
emit!(Call(
|
||||||
|
Exec::call(if meta.is_dir() { "cd" } else { "reveal" }, vec![
|
||||||
|
p.to_string_lossy().to_string()
|
||||||
|
])
|
||||||
|
.vec(),
|
||||||
|
KeymapLayer::Manager
|
||||||
|
));
|
||||||
}
|
}
|
||||||
Err(InputError::Completed(before, ticket)) => {
|
Err(InputError::Completed(before, ticket)) => {
|
||||||
emit!(Call(
|
emit!(Call(
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,25 @@
|
||||||
use std::ffi::{OsStr, OsString};
|
use std::ffi::{OsStr, OsString};
|
||||||
|
|
||||||
|
use yazi_config::keymap::Exec;
|
||||||
|
|
||||||
use crate::{external, tab::Tab};
|
use crate::{external, tab::Tab};
|
||||||
|
|
||||||
|
pub struct Opt<'a> {
|
||||||
|
type_: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a Exec> for Opt<'a> {
|
||||||
|
fn from(e: &'a Exec) -> Self { Self { type_: e.args.first().map(|s| s.as_str()).unwrap_or("") } }
|
||||||
|
}
|
||||||
|
|
||||||
impl Tab {
|
impl Tab {
|
||||||
pub fn copy(&self, type_: &str) -> bool {
|
pub fn copy<'a>(&self, opt: impl Into<Opt<'a>>) -> bool {
|
||||||
|
let opt = opt.into() as Opt;
|
||||||
|
|
||||||
let mut s = OsString::new();
|
let mut s = OsString::new();
|
||||||
let mut it = self.selected().into_iter().peekable();
|
let mut it = self.selected().into_iter().peekable();
|
||||||
while let Some(f) = it.next() {
|
while let Some(f) = it.next() {
|
||||||
s.push(match type_ {
|
s.push(match opt.type_ {
|
||||||
"path" => f.url.as_os_str(),
|
"path" => f.url.as_os_str(),
|
||||||
"dirname" => f.url.parent().map_or(OsStr::new(""), |p| p.as_os_str()),
|
"dirname" => f.url.parent().map_or(OsStr::new(""), |p| p.as_os_str()),
|
||||||
"filename" => f.name().unwrap_or(OsStr::new("")),
|
"filename" => f.name().unwrap_or(OsStr::new("")),
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,19 @@
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
|
||||||
|
use yazi_config::keymap::Exec;
|
||||||
|
|
||||||
use crate::{emit, tab::Tab};
|
use crate::{emit, tab::Tab};
|
||||||
|
|
||||||
|
pub struct Opt;
|
||||||
|
impl From<()> for Opt {
|
||||||
|
fn from(_: ()) -> Self { Self }
|
||||||
|
}
|
||||||
|
impl From<&Exec> for Opt {
|
||||||
|
fn from(_: &Exec) -> Self { Self }
|
||||||
|
}
|
||||||
|
|
||||||
impl Tab {
|
impl Tab {
|
||||||
pub fn enter(&mut self) -> bool {
|
pub fn enter(&mut self, _: impl Into<Opt>) -> bool {
|
||||||
let Some(hovered) = self.current.hovered().filter(|h| h.is_dir()).map(|h| h.url()) else {
|
let Some(hovered) = self.current.hovered().filter(|h| h.is_dir()).map(|h| h.url()) else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,27 @@
|
||||||
|
use bitflags::bitflags;
|
||||||
use yazi_config::keymap::Exec;
|
use yazi_config::keymap::Exec;
|
||||||
|
|
||||||
use crate::tab::{Mode, Tab};
|
use crate::tab::{Mode, Tab};
|
||||||
|
|
||||||
pub struct Opt(u8);
|
bitflags! {
|
||||||
|
pub struct Opt: u8 {
|
||||||
|
const FIND = 0b0001;
|
||||||
|
const VISUAL = 0b0010;
|
||||||
|
const SELECT = 0b0100;
|
||||||
|
const SEARCH = 0b1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<&Exec> for Opt {
|
impl From<&Exec> for Opt {
|
||||||
fn from(e: &Exec) -> Self {
|
fn from(e: &Exec) -> Self {
|
||||||
Self(e.named.iter().fold(0, |acc, (k, _)| match k.as_str() {
|
e.named.iter().fold(Opt::empty(), |acc, (k, _)| match k.as_bytes() {
|
||||||
"all" => 0b1111,
|
b"all" => Self::all(),
|
||||||
"find" => acc | 0b0001,
|
b"find" => acc | Self::FIND,
|
||||||
"visual" => acc | 0b0010,
|
b"visual" => acc | Self::VISUAL,
|
||||||
"select" => acc | 0b0100,
|
b"select" => acc | Self::SELECT,
|
||||||
"search" => acc | 0b1000,
|
b"search" => acc | Self::SEARCH,
|
||||||
_ => acc,
|
_ => acc,
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -38,8 +46,8 @@ impl Tab {
|
||||||
fn escape_search(&mut self) -> bool { self.search_stop() }
|
fn escape_search(&mut self) -> bool { self.search_stop() }
|
||||||
|
|
||||||
pub fn escape(&mut self, opt: impl Into<Opt>) -> bool {
|
pub fn escape(&mut self, opt: impl Into<Opt>) -> bool {
|
||||||
let opt = opt.into().0;
|
let opt = opt.into() as Opt;
|
||||||
if opt == 0 {
|
if opt.is_empty() {
|
||||||
return self.escape_find()
|
return self.escape_find()
|
||||||
|| self.escape_visual()
|
|| self.escape_visual()
|
||||||
|| self.escape_select()
|
|| self.escape_select()
|
||||||
|
|
@ -47,16 +55,16 @@ impl Tab {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut b = false;
|
let mut b = false;
|
||||||
if opt & 0b0001 != 0 {
|
if opt.contains(Opt::FIND) {
|
||||||
b |= self.escape_find();
|
b |= self.escape_find();
|
||||||
}
|
}
|
||||||
if opt & 0b0010 != 0 {
|
if opt.contains(Opt::VISUAL) {
|
||||||
b |= self.escape_visual();
|
b |= self.escape_visual();
|
||||||
}
|
}
|
||||||
if opt & 0b0100 != 0 {
|
if opt.contains(Opt::SELECT) {
|
||||||
b |= self.escape_select();
|
b |= self.escape_select();
|
||||||
}
|
}
|
||||||
if opt & 0b1000 != 0 {
|
if opt.contains(Opt::SEARCH) {
|
||||||
b |= self.escape_search();
|
b |= self.escape_search();
|
||||||
}
|
}
|
||||||
b
|
b
|
||||||
|
|
|
||||||
|
|
@ -7,30 +7,40 @@ use yazi_shared::{Debounce, InputError};
|
||||||
|
|
||||||
use crate::{emit, input::InputOpt, tab::{Finder, FinderCase, Tab}};
|
use crate::{emit, input::InputOpt, tab::{Finder, FinderCase, Tab}};
|
||||||
|
|
||||||
impl Tab {
|
pub struct Opt<'a> {
|
||||||
pub fn find(&mut self, query: Option<&str>, prev: bool, case: FinderCase) -> bool {
|
query: Option<&'a str>,
|
||||||
if let Some(query) = query {
|
prev: bool,
|
||||||
let Ok(finder) = Finder::new(query, case) else {
|
case: FinderCase,
|
||||||
return false;
|
}
|
||||||
};
|
|
||||||
|
|
||||||
let step = if prev {
|
impl<'a> From<&'a Exec> for Opt<'a> {
|
||||||
finder.prev(&self.current.files, self.current.cursor, true)
|
fn from(e: &'a Exec) -> Self {
|
||||||
} else {
|
Self {
|
||||||
finder.next(&self.current.files, self.current.cursor, true)
|
query: e.args.first().map(|s| s.as_str()),
|
||||||
};
|
prev: e.named.contains_key("previous"),
|
||||||
|
case: match (e.named.contains_key("smart"), e.named.contains_key("insensitive")) {
|
||||||
if let Some(step) = step {
|
(true, _) => FinderCase::Smart,
|
||||||
self.arrow(step.into());
|
(_, false) => FinderCase::Sensitive,
|
||||||
}
|
(_, true) => FinderCase::Insensitive,
|
||||||
|
},
|
||||||
self.finder = Some(finder);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ArrowOpt {
|
||||||
|
prev: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Exec> for ArrowOpt {
|
||||||
|
fn from(e: &Exec) -> Self { Self { prev: e.named.contains_key("previous") } }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tab {
|
||||||
|
pub fn find<'a>(&mut self, opt: impl Into<Opt<'a>>) -> bool {
|
||||||
|
let opt = opt.into() as Opt;
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let rx = emit!(Input(
|
let rx = emit!(Input(
|
||||||
InputOpt::top(if prev { "Find previous:" } else { "Find next:" }).with_realtime()
|
InputOpt::top(if opt.prev { "Find previous:" } else { "Find next:" }).with_realtime()
|
||||||
));
|
));
|
||||||
|
|
||||||
let rx = Debounce::new(UnboundedReceiverStream::new(rx), Duration::from_millis(50));
|
let rx = Debounce::new(UnboundedReceiverStream::new(rx), Duration::from_millis(50));
|
||||||
|
|
@ -38,10 +48,10 @@ impl Tab {
|
||||||
|
|
||||||
while let Some(Ok(s)) | Some(Err(InputError::Typed(s))) = rx.next().await {
|
while let Some(Ok(s)) | Some(Err(InputError::Typed(s))) = rx.next().await {
|
||||||
emit!(Call(
|
emit!(Call(
|
||||||
Exec::call("find", vec![s])
|
Exec::call("find_do", vec![s])
|
||||||
.with_bool("previous", prev)
|
.with_bool("previous", opt.prev)
|
||||||
.with_bool("smart", case == FinderCase::Smart)
|
.with_bool("smart", opt.case == FinderCase::Smart)
|
||||||
.with_bool("insensitive", case == FinderCase::Insensitive)
|
.with_bool("insensitive", opt.case == FinderCase::Insensitive)
|
||||||
.vec(),
|
.vec(),
|
||||||
KeymapLayer::Manager
|
KeymapLayer::Manager
|
||||||
));
|
));
|
||||||
|
|
@ -50,18 +60,42 @@ impl Tab {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_arrow(&mut self, prev: bool) -> bool {
|
pub fn find_do<'a>(&mut self, opt: impl Into<Opt<'a>>) -> bool {
|
||||||
|
let opt = opt.into() as Opt;
|
||||||
|
let Some(query) = opt.query else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Ok(finder) = Finder::new(query, opt.case) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
let step = if opt.prev {
|
||||||
|
finder.prev(&self.current.files, self.current.cursor, true)
|
||||||
|
} else {
|
||||||
|
finder.next(&self.current.files, self.current.cursor, true)
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(step) = step {
|
||||||
|
self.arrow(step);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.finder = Some(finder);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find_arrow(&mut self, opt: impl Into<ArrowOpt>) -> bool {
|
||||||
let Some(finder) = &mut self.finder else {
|
let Some(finder) = &mut self.finder else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
let b = finder.catchup(&self.current.files);
|
let b = finder.catchup(&self.current.files);
|
||||||
let step = if prev {
|
let step = if opt.into().prev {
|
||||||
finder.prev(&self.current.files, self.current.cursor, false)
|
finder.prev(&self.current.files, self.current.cursor, false)
|
||||||
} else {
|
} else {
|
||||||
finder.next(&self.current.files, self.current.cursor, false)
|
finder.next(&self.current.files, self.current.cursor, false)
|
||||||
};
|
};
|
||||||
|
|
||||||
b | step.is_some_and(|s| self.arrow(s.into()))
|
b | step.is_some_and(|s| self.arrow(s))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,52 @@
|
||||||
use yazi_shared::Defer;
|
use yazi_config::keymap::{Exec, KeymapLayer};
|
||||||
|
use yazi_shared::{ends_with_slash, Defer};
|
||||||
|
|
||||||
use crate::{emit, external::{self, FzfOpt, ZoxideOpt}, tab::Tab, Event, BLOCKER};
|
use crate::{emit, external::{self, FzfOpt, ZoxideOpt}, tab::Tab, Event, BLOCKER};
|
||||||
|
|
||||||
impl Tab {
|
pub struct Opt {
|
||||||
pub fn jump(&self, global: bool) -> bool {
|
type_: OptType,
|
||||||
let cwd = self.current.cwd.clone();
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq)]
|
||||||
|
pub enum OptType {
|
||||||
|
None,
|
||||||
|
Fzf,
|
||||||
|
Zoxide,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Exec> for Opt {
|
||||||
|
fn from(e: &Exec) -> Self {
|
||||||
|
Self {
|
||||||
|
type_: match e.args.first().map(|s| s.as_str()) {
|
||||||
|
Some("fzf") => OptType::Fzf,
|
||||||
|
Some("zoxide") => OptType::Zoxide,
|
||||||
|
_ => OptType::None,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tab {
|
||||||
|
pub fn jump(&self, opt: impl Into<Opt>) -> bool {
|
||||||
|
let opt = opt.into() as Opt;
|
||||||
|
if opt.type_ == OptType::None {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cwd = self.current.cwd.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let _guard = BLOCKER.acquire().await.unwrap();
|
let _guard = BLOCKER.acquire().await.unwrap();
|
||||||
let _defer = Defer::new(|| Event::Stop(false, None).emit());
|
let _defer = Defer::new(|| Event::Stop(false, None).emit());
|
||||||
emit!(Stop(true)).await;
|
emit!(Stop(true)).await;
|
||||||
|
|
||||||
let rx =
|
let url = if opt.type_ == OptType::Fzf {
|
||||||
if global { external::fzf(FzfOpt { cwd }) } else { external::zoxide(ZoxideOpt { cwd }) }?;
|
external::fzf(FzfOpt { cwd }).await
|
||||||
|
} else {
|
||||||
|
external::zoxide(ZoxideOpt { cwd }).await
|
||||||
|
}?;
|
||||||
|
|
||||||
if let Ok(target) = rx.await? {
|
let op = if opt.type_ == OptType::Fzf && !ends_with_slash(&url) { "reveal" } else { "cd" };
|
||||||
emit!(Cd(target));
|
emit!(Call(Exec::call(op, vec![url.to_string()]).vec(), KeymapLayer::Manager));
|
||||||
}
|
|
||||||
Ok::<(), anyhow::Error>(())
|
Ok::<(), anyhow::Error>(())
|
||||||
});
|
});
|
||||||
false
|
false
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,19 @@
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
|
||||||
|
use yazi_config::keymap::Exec;
|
||||||
|
|
||||||
use crate::{emit, tab::Tab};
|
use crate::{emit, tab::Tab};
|
||||||
|
|
||||||
|
pub struct Opt;
|
||||||
|
impl From<()> for Opt {
|
||||||
|
fn from(_: ()) -> Self { Self }
|
||||||
|
}
|
||||||
|
impl From<&Exec> for Opt {
|
||||||
|
fn from(_: &Exec) -> Self { Self }
|
||||||
|
}
|
||||||
|
|
||||||
impl Tab {
|
impl Tab {
|
||||||
pub fn leave(&mut self) -> bool {
|
pub fn leave(&mut self, _: impl Into<Opt>) -> bool {
|
||||||
let current = self
|
let current = self
|
||||||
.current
|
.current
|
||||||
.hovered()
|
.hovered()
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ mod hidden;
|
||||||
mod jump;
|
mod jump;
|
||||||
mod leave;
|
mod leave;
|
||||||
mod linemode;
|
mod linemode;
|
||||||
|
mod reveal;
|
||||||
mod search;
|
mod search;
|
||||||
mod select;
|
mod select;
|
||||||
mod shell;
|
mod shell;
|
||||||
|
|
|
||||||
32
yazi-core/src/tab/commands/reveal.rs
Normal file
32
yazi-core/src/tab/commands/reveal.rs
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
use yazi_config::keymap::Exec;
|
||||||
|
use yazi_shared::{expand_path, Url};
|
||||||
|
|
||||||
|
use crate::{emit, files::{File, FilesOp}, tab::Tab};
|
||||||
|
|
||||||
|
pub struct Opt {
|
||||||
|
target: Url,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Exec> for Opt {
|
||||||
|
fn from(e: &Exec) -> Self {
|
||||||
|
Self { target: Url::from(expand_path(e.args.first().map(|s| s.as_str()).unwrap_or(""))) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tab {
|
||||||
|
pub fn reveal(&mut self, opt: impl Into<Opt>) -> bool {
|
||||||
|
let opt = opt.into() as Opt;
|
||||||
|
|
||||||
|
let Some(parent) = opt.target.parent_url() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
let b = self.cd(parent.clone());
|
||||||
|
emit!(Files(FilesOp::Creating(
|
||||||
|
parent.clone(),
|
||||||
|
File::from_dummy(opt.target.clone()).into_map()
|
||||||
|
)));
|
||||||
|
emit!(Hover(opt.target));
|
||||||
|
b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,11 +3,40 @@ use std::{mem, time::Duration};
|
||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
use tokio::pin;
|
use tokio::pin;
|
||||||
use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt};
|
use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt};
|
||||||
|
use yazi_config::keymap::{Exec, KeymapLayer};
|
||||||
|
|
||||||
use crate::{emit, external, files::FilesOp, input::InputOpt, tab::Tab};
|
use crate::{emit, external, files::FilesOp, input::InputOpt, tab::Tab};
|
||||||
|
|
||||||
|
pub struct Opt {
|
||||||
|
pub type_: OptType,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq)]
|
||||||
|
pub enum OptType {
|
||||||
|
None,
|
||||||
|
Rg,
|
||||||
|
Fd,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Exec> for Opt {
|
||||||
|
fn from(e: &Exec) -> Self {
|
||||||
|
Self {
|
||||||
|
type_: match e.args.first().map(|s| s.as_str()) {
|
||||||
|
Some("fd") => OptType::Fd,
|
||||||
|
Some("rg") => OptType::Rg,
|
||||||
|
_ => OptType::None,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Tab {
|
impl Tab {
|
||||||
pub fn search(&mut self, grep: bool) -> bool {
|
pub fn search(&mut self, opt: impl Into<Opt>) -> bool {
|
||||||
|
let opt = opt.into() as Opt;
|
||||||
|
if opt.type_ == OptType::None {
|
||||||
|
return self.search_stop();
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(handle) = self.search.take() {
|
if let Some(handle) = self.search.take() {
|
||||||
handle.abort();
|
handle.abort();
|
||||||
}
|
}
|
||||||
|
|
@ -17,11 +46,11 @@ impl Tab {
|
||||||
|
|
||||||
self.search = Some(tokio::spawn(async move {
|
self.search = Some(tokio::spawn(async move {
|
||||||
let Some(Ok(subject)) = emit!(Input(InputOpt::top("Search:"))).recv().await else {
|
let Some(Ok(subject)) = emit!(Input(InputOpt::top("Search:"))).recv().await else {
|
||||||
bail!("canceled")
|
bail!("")
|
||||||
};
|
};
|
||||||
|
|
||||||
cwd = cwd.into_search(subject.clone());
|
cwd = cwd.into_search(subject.clone());
|
||||||
let rx = if grep {
|
let rx = if opt.type_ == OptType::Rg {
|
||||||
external::rg(external::RgOpt { cwd: cwd.clone(), hidden, subject })
|
external::rg(external::RgOpt { cwd: cwd.clone(), hidden, subject })
|
||||||
} else {
|
} else {
|
||||||
external::fd(external::FdOpt { cwd: cwd.clone(), hidden, glob: false, subject })
|
external::fd(external::FdOpt { cwd: cwd.clone(), hidden, glob: false, subject })
|
||||||
|
|
@ -34,7 +63,7 @@ impl Tab {
|
||||||
let mut first = true;
|
let mut first = true;
|
||||||
while let Some(chunk) = rx.next().await {
|
while let Some(chunk) = rx.next().await {
|
||||||
if first {
|
if first {
|
||||||
emit!(Cd(cwd.clone()));
|
emit!(Call(Exec::call("cd", vec![cwd.clone().to_string()]).vec(), KeymapLayer::Manager));
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
emit!(Files(FilesOp::Part(cwd.clone(), ticket, chunk)));
|
emit!(Files(FilesOp::Part(cwd.clone(), ticket, chunk)));
|
||||||
|
|
@ -44,7 +73,7 @@ impl Tab {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn search_stop(&mut self) -> bool {
|
pub(super) fn search_stop(&mut self) -> bool {
|
||||||
if let Some(handle) = self.search.take() {
|
if let Some(handle) = self.search.take() {
|
||||||
handle.abort();
|
handle.abort();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,35 @@
|
||||||
|
use yazi_config::keymap::Exec;
|
||||||
|
|
||||||
use crate::tab::Tab;
|
use crate::tab::Tab;
|
||||||
|
|
||||||
|
pub struct Opt {
|
||||||
|
state: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Exec> for Opt {
|
||||||
|
fn from(e: &Exec) -> Self {
|
||||||
|
Self {
|
||||||
|
state: match e.named.get("state").map(|s| s.as_bytes()) {
|
||||||
|
Some(b"true") => Some(true),
|
||||||
|
Some(b"false") => Some(false),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<Option<bool>> for Opt {
|
||||||
|
fn from(state: Option<bool>) -> Self { Self { state } }
|
||||||
|
}
|
||||||
|
|
||||||
impl Tab {
|
impl Tab {
|
||||||
pub fn select(&mut self, state: Option<bool>) -> bool {
|
pub fn select(&mut self, opt: impl Into<Opt>) -> bool {
|
||||||
if let Some(u) = self.current.hovered().map(|h| h.url()) {
|
if let Some(u) = self.current.hovered().map(|h| h.url()) {
|
||||||
return self.current.files.select(&u, state);
|
return self.current.files.select(&u, opt.into().state);
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select_all(&mut self, state: Option<bool>) -> bool { self.current.files.select_all(state) }
|
pub fn select_all(&mut self, opt: impl Into<Opt>) -> bool {
|
||||||
|
self.current.files.select_all(opt.into().state)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,41 @@
|
||||||
use yazi_config::open::Opener;
|
use yazi_config::{keymap::Exec, open::Opener};
|
||||||
|
|
||||||
use crate::{emit, input::InputOpt, tab::Tab};
|
use crate::{emit, input::InputOpt, tab::Tab};
|
||||||
|
|
||||||
|
pub struct Opt {
|
||||||
|
cmd: String,
|
||||||
|
block: bool,
|
||||||
|
confirm: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a Exec> for Opt {
|
||||||
|
fn from(e: &'a Exec) -> Self {
|
||||||
|
Self {
|
||||||
|
cmd: e.args.first().map(|e| e.to_owned()).unwrap_or_default(),
|
||||||
|
block: e.named.contains_key("block"),
|
||||||
|
confirm: e.named.contains_key("confirm"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Tab {
|
impl Tab {
|
||||||
pub fn shell(&self, exec: &str, block: bool, confirm: bool) -> bool {
|
pub fn shell(&self, opt: impl Into<Opt>) -> bool {
|
||||||
let selected: Vec<_> = self
|
let selected: Vec<_> = self
|
||||||
.selected()
|
.selected()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|f| (f.url.as_os_str().to_owned(), Default::default()))
|
.map(|f| (f.url.as_os_str().to_owned(), Default::default()))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let mut exec = exec.to_owned();
|
let mut opt = opt.into() as Opt;
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
if !confirm || exec.is_empty() {
|
if !opt.confirm || opt.cmd.is_empty() {
|
||||||
let mut result = emit!(Input(
|
let mut result = emit!(Input(
|
||||||
InputOpt::top(if block { "Shell (block):" } else { "Shell:" })
|
InputOpt::top(if opt.block { "Shell (block):" } else { "Shell:" })
|
||||||
.with_value(&exec)
|
.with_value(opt.cmd)
|
||||||
.with_highlight()
|
.with_highlight()
|
||||||
));
|
));
|
||||||
match result.recv().await {
|
match result.recv().await {
|
||||||
Some(Ok(e)) => exec = e,
|
Some(Ok(e)) => opt.cmd = e,
|
||||||
_ => return,
|
_ => return,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -27,12 +43,12 @@ impl Tab {
|
||||||
emit!(Open(
|
emit!(Open(
|
||||||
selected,
|
selected,
|
||||||
Some(Opener {
|
Some(Opener {
|
||||||
exec,
|
exec: opt.cmd,
|
||||||
block,
|
block: opt.block,
|
||||||
orphan: false,
|
orphan: false,
|
||||||
desc: Default::default(),
|
desc: Default::default(),
|
||||||
for_: None,
|
for_: None,
|
||||||
spread: true
|
spread: true,
|
||||||
})
|
})
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,23 @@
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
|
use yazi_config::keymap::Exec;
|
||||||
|
|
||||||
use crate::tab::{Mode, Tab};
|
use crate::tab::{Mode, Tab};
|
||||||
|
|
||||||
|
pub struct Opt {
|
||||||
|
unset: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Exec> for Opt {
|
||||||
|
fn from(e: &Exec) -> Self { Self { unset: e.named.contains_key("unset") } }
|
||||||
|
}
|
||||||
|
|
||||||
impl Tab {
|
impl Tab {
|
||||||
pub fn visual_mode(&mut self, unset: bool) -> bool {
|
pub fn visual_mode(&mut self, opt: impl Into<Opt>) -> bool {
|
||||||
|
let opt = opt.into() as Opt;
|
||||||
let idx = self.current.cursor;
|
let idx = self.current.cursor;
|
||||||
|
|
||||||
if unset {
|
if opt.unset {
|
||||||
self.mode = Mode::Unset(idx, BTreeSet::from([idx]));
|
self.mode = Mode::Unset(idx, BTreeSet::from([idx]));
|
||||||
} else {
|
} else {
|
||||||
self.mode = Mode::Select(idx, BTreeSet::from([idx]));
|
self.mode = Mode::Select(idx, BTreeSet::from([idx]));
|
||||||
|
|
|
||||||
|
|
@ -43,22 +43,38 @@ impl Folder {
|
||||||
self.prev(Default::default());
|
self.prev(Default::default());
|
||||||
|
|
||||||
if self.page == old {
|
if self.page == old {
|
||||||
emit!(Pages(self.page)); // Force update
|
self.set_page(true); // Force update
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_page(&mut self, force: bool) -> bool {
|
pub fn set_page(&mut self, force: bool) {
|
||||||
let limit = MANAGER.layout.folder_height();
|
let limit = MANAGER.layout.folder_height();
|
||||||
let new = if limit == 0 { 0 } else { self.cursor / limit };
|
if limit == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let new = self.cursor / limit;
|
||||||
if !force && self.page == new {
|
if !force && self.page == new {
|
||||||
return false;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Current page
|
||||||
|
emit!(Pages(new));
|
||||||
|
|
||||||
|
// Next page
|
||||||
|
let max_page = (self.files.len() + limit - 1) / limit;
|
||||||
|
if new < max_page && new + 1 != self.page {
|
||||||
|
emit!(Pages(new + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Previous page
|
||||||
|
if new > 1 && new - 1 != self.page {
|
||||||
|
emit!(Pages(new - 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.page = new;
|
self.page = new;
|
||||||
emit!(Pages(new));
|
|
||||||
true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn next(&mut self, step: Step) -> bool {
|
pub fn next(&mut self, step: Step) -> bool {
|
||||||
|
|
@ -115,11 +131,11 @@ impl Folder {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn hovered(&self) -> Option<&File> { self.files.get(self.cursor) }
|
pub fn hovered(&self) -> Option<&File> { self.files.get(self.cursor) }
|
||||||
|
|
||||||
pub fn paginate(&self) -> &[File] {
|
pub fn paginate(&self, page: usize) -> &[File] {
|
||||||
let len = self.files.len();
|
let len = self.files.len();
|
||||||
let limit = MANAGER.layout.folder_height();
|
let limit = MANAGER.layout.folder_height();
|
||||||
|
|
||||||
let start = (self.page * limit).min(len.saturating_sub(1));
|
let start = (page * limit).min(len.saturating_sub(1));
|
||||||
let end = (start + limit).min(len);
|
let end = (start + limit).min(len);
|
||||||
&self.files[start..end]
|
&self.files[start..end]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
36
yazi-core/src/tasks/commands/arrow.rs
Normal file
36
yazi-core/src/tasks/commands/arrow.rs
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
use yazi_config::keymap::Exec;
|
||||||
|
|
||||||
|
use crate::tasks::Tasks;
|
||||||
|
|
||||||
|
pub struct Opt {
|
||||||
|
step: isize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Exec> for Opt {
|
||||||
|
fn from(e: &Exec) -> Self {
|
||||||
|
Self { step: e.args.first().and_then(|s| s.parse().ok()).unwrap_or(0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tasks {
|
||||||
|
#[allow(clippy::should_implement_trait)]
|
||||||
|
fn next(&mut self) -> bool {
|
||||||
|
let limit = Self::limit().min(self.len());
|
||||||
|
|
||||||
|
let old = self.cursor;
|
||||||
|
self.cursor = limit.saturating_sub(1).min(self.cursor + 1);
|
||||||
|
|
||||||
|
old != self.cursor
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prev(&mut self) -> bool {
|
||||||
|
let old = self.cursor;
|
||||||
|
self.cursor = self.cursor.saturating_sub(1);
|
||||||
|
old != self.cursor
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn arrow(&mut self, opt: impl Into<Opt>) -> bool {
|
||||||
|
let opt = opt.into() as Opt;
|
||||||
|
if opt.step > 0 { self.next() } else { self.prev() }
|
||||||
|
}
|
||||||
|
}
|
||||||
22
yazi-core/src/tasks/commands/cancel.rs
Normal file
22
yazi-core/src/tasks/commands/cancel.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
use yazi_config::keymap::Exec;
|
||||||
|
|
||||||
|
use crate::tasks::Tasks;
|
||||||
|
|
||||||
|
pub struct Opt;
|
||||||
|
|
||||||
|
impl From<&Exec> for Opt {
|
||||||
|
fn from(_: &Exec) -> Self { Self }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tasks {
|
||||||
|
pub fn cancel(&mut self, _: impl Into<Opt>) -> bool {
|
||||||
|
let id = self.scheduler.running.read().get_id(self.cursor);
|
||||||
|
if id.map(|id| self.scheduler.cancel(id)) != Some(true) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let len = self.scheduler.running.read().len();
|
||||||
|
self.cursor = self.cursor.min(len.saturating_sub(1));
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
77
yazi-core/src/tasks/commands/inspect.rs
Normal file
77
yazi-core/src/tasks/commands/inspect.rs
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
use std::io::{stdout, Write};
|
||||||
|
|
||||||
|
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
||||||
|
use tokio::{io::{stdin, AsyncReadExt}, select, sync::mpsc, time};
|
||||||
|
use yazi_config::keymap::Exec;
|
||||||
|
use yazi_shared::{Defer, Term};
|
||||||
|
|
||||||
|
use crate::{emit, tasks::Tasks, Event, BLOCKER};
|
||||||
|
|
||||||
|
pub struct Opt;
|
||||||
|
|
||||||
|
impl From<&Exec> for Opt {
|
||||||
|
fn from(_: &Exec) -> Self { Self }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tasks {
|
||||||
|
pub fn inspect(&self, _: impl Into<Opt>) -> bool {
|
||||||
|
let Some(id) = self.scheduler.running.read().get_id(self.cursor) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
let scheduler = self.scheduler.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let _guard = BLOCKER.acquire().await.unwrap();
|
||||||
|
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||||
|
|
||||||
|
let buffered = {
|
||||||
|
let mut running = scheduler.running.write();
|
||||||
|
let Some(task) = running.get_mut(id) else { return };
|
||||||
|
|
||||||
|
task.logger = Some(tx);
|
||||||
|
task.logs.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
emit!(Stop(true)).await;
|
||||||
|
let _defer = Defer::new(|| {
|
||||||
|
disable_raw_mode().ok();
|
||||||
|
Event::Stop(false, None).emit();
|
||||||
|
});
|
||||||
|
|
||||||
|
Term::clear(&mut stdout()).ok();
|
||||||
|
stdout().write_all(buffered.as_bytes()).ok();
|
||||||
|
enable_raw_mode().ok();
|
||||||
|
|
||||||
|
let mut stdin = stdin();
|
||||||
|
let mut quit = [0; 10];
|
||||||
|
loop {
|
||||||
|
select! {
|
||||||
|
Some(line) = rx.recv() => {
|
||||||
|
let mut stdout = stdout().lock();
|
||||||
|
stdout.write_all(line.as_bytes()).ok();
|
||||||
|
stdout.write_all(b"\r\n").ok();
|
||||||
|
}
|
||||||
|
_ = time::sleep(time::Duration::from_millis(100)) => {
|
||||||
|
if scheduler.running.read().get(id).is_none() {
|
||||||
|
stdout().write_all(b"Task finished, press `q` to quit\r\n").ok();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Ok(_) = stdin.read(&mut quit) => {
|
||||||
|
if quit[0] == b'q' {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(task) = scheduler.running.write().get_mut(id) {
|
||||||
|
task.logger = None;
|
||||||
|
}
|
||||||
|
while quit[0] != b'q' {
|
||||||
|
stdin.read(&mut quit).await.ok();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
4
yazi-core/src/tasks/commands/mod.rs
Normal file
4
yazi-core/src/tasks/commands/mod.rs
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
mod arrow;
|
||||||
|
mod cancel;
|
||||||
|
mod inspect;
|
||||||
|
mod toggle;
|
||||||
20
yazi-core/src/tasks/commands/toggle.rs
Normal file
20
yazi-core/src/tasks/commands/toggle.rs
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
use yazi_config::keymap::Exec;
|
||||||
|
|
||||||
|
use crate::{emit, tasks::Tasks};
|
||||||
|
|
||||||
|
pub struct Opt;
|
||||||
|
|
||||||
|
impl From<&Exec> for Opt {
|
||||||
|
fn from(_: &Exec) -> Self { Self }
|
||||||
|
}
|
||||||
|
impl From<()> for Opt {
|
||||||
|
fn from(_: ()) -> Self { Self }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tasks {
|
||||||
|
pub fn toggle(&mut self, _: impl Into<Opt>) -> bool {
|
||||||
|
self.visible = !self.visible;
|
||||||
|
emit!(Peek); // Show/hide preview for images
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
mod commands;
|
||||||
mod running;
|
mod running;
|
||||||
mod scheduler;
|
mod scheduler;
|
||||||
mod task;
|
mod task;
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,15 @@
|
||||||
use std::{collections::{BTreeMap, HashMap, HashSet}, ffi::OsStr, io::{stdout, Write}, path::Path, sync::Arc};
|
use std::{collections::{BTreeMap, HashMap, HashSet}, ffi::OsStr, path::Path, sync::Arc};
|
||||||
|
|
||||||
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use tokio::{io::{stdin, AsyncReadExt}, select, sync::mpsc, time};
|
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
use yazi_config::{manager::SortBy, open::Opener, OPEN};
|
use yazi_config::{manager::SortBy, open::Opener, OPEN};
|
||||||
use yazi_shared::{Defer, MimeKind, Term, Url};
|
use yazi_shared::{MimeKind, Term, Url};
|
||||||
|
|
||||||
use super::{running::Running, task::TaskSummary, Scheduler, TASKS_PADDING, TASKS_PERCENT};
|
use super::{running::Running, task::TaskSummary, Scheduler, TASKS_PADDING, TASKS_PERCENT};
|
||||||
use crate::{emit, files::{File, Files}, input::InputOpt, Event, BLOCKER};
|
use crate::{emit, files::{File, Files}, input::InputOpt};
|
||||||
|
|
||||||
pub struct Tasks {
|
pub struct Tasks {
|
||||||
scheduler: Arc<Scheduler>,
|
pub(super) scheduler: Arc<Scheduler>,
|
||||||
|
|
||||||
pub visible: bool,
|
pub visible: bool,
|
||||||
pub cursor: usize,
|
pub cursor: usize,
|
||||||
|
|
@ -33,105 +31,11 @@ impl Tasks {
|
||||||
(Term::size().rows * TASKS_PERCENT / 100).saturating_sub(TASKS_PADDING) as usize
|
(Term::size().rows * TASKS_PERCENT / 100).saturating_sub(TASKS_PADDING) as usize
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toggle(&mut self) -> bool {
|
|
||||||
self.visible = !self.visible;
|
|
||||||
emit!(Peek); // Show/hide preview for images
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::should_implement_trait)]
|
|
||||||
pub fn next(&mut self) -> bool {
|
|
||||||
let limit = Self::limit().min(self.len());
|
|
||||||
|
|
||||||
let old = self.cursor;
|
|
||||||
self.cursor = limit.saturating_sub(1).min(self.cursor + 1);
|
|
||||||
|
|
||||||
old != self.cursor
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn prev(&mut self) -> bool {
|
|
||||||
let old = self.cursor;
|
|
||||||
self.cursor = self.cursor.saturating_sub(1);
|
|
||||||
old != self.cursor
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn paginate(&self) -> Vec<TaskSummary> {
|
pub fn paginate(&self) -> Vec<TaskSummary> {
|
||||||
let running = self.scheduler.running.read();
|
let running = self.scheduler.running.read();
|
||||||
running.values().take(Self::limit()).map(Into::into).collect()
|
running.values().take(Self::limit()).map(Into::into).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn inspect(&self) -> bool {
|
|
||||||
let Some(id) = self.scheduler.running.read().get_id(self.cursor) else {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
let scheduler = self.scheduler.clone();
|
|
||||||
tokio::spawn(async move {
|
|
||||||
let _guard = BLOCKER.acquire().await.unwrap();
|
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
|
||||||
|
|
||||||
let buffered = {
|
|
||||||
let mut running = scheduler.running.write();
|
|
||||||
let Some(task) = running.get_mut(id) else { return };
|
|
||||||
|
|
||||||
task.logger = Some(tx);
|
|
||||||
task.logs.clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
emit!(Stop(true)).await;
|
|
||||||
let _defer = Defer::new(|| {
|
|
||||||
disable_raw_mode().ok();
|
|
||||||
Event::Stop(false, None).emit();
|
|
||||||
});
|
|
||||||
|
|
||||||
Term::clear(&mut stdout()).ok();
|
|
||||||
stdout().write_all(buffered.as_bytes()).ok();
|
|
||||||
enable_raw_mode().ok();
|
|
||||||
|
|
||||||
let mut stdin = stdin();
|
|
||||||
let mut quit = [0; 10];
|
|
||||||
loop {
|
|
||||||
select! {
|
|
||||||
Some(line) = rx.recv() => {
|
|
||||||
let mut stdout = stdout().lock();
|
|
||||||
stdout.write_all(line.as_bytes()).ok();
|
|
||||||
stdout.write_all(b"\r\n").ok();
|
|
||||||
}
|
|
||||||
_ = time::sleep(time::Duration::from_millis(100)) => {
|
|
||||||
if scheduler.running.read().get(id).is_none() {
|
|
||||||
stdout().write_all(b"Task finished, press `q` to quit\r\n").ok();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Ok(_) = stdin.read(&mut quit) => {
|
|
||||||
if quit[0] == b'q' {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(task) = scheduler.running.write().get_mut(id) {
|
|
||||||
task.logger = None;
|
|
||||||
}
|
|
||||||
while quit[0] != b'q' {
|
|
||||||
stdin.read(&mut quit).await.ok();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cancel(&mut self) -> bool {
|
|
||||||
let id = self.scheduler.running.read().get_id(self.cursor);
|
|
||||||
if id.map(|id| self.scheduler.cancel(id)) != Some(true) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let len = self.scheduler.running.read().len();
|
|
||||||
self.cursor = self.cursor.min(len.saturating_sub(1));
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn file_open(&self, targets: &[(impl AsRef<Path>, impl AsRef<str>)]) -> bool {
|
pub fn file_open(&self, targets: &[(impl AsRef<Path>, impl AsRef<str>)]) -> bool {
|
||||||
let mut openers = BTreeMap::new();
|
let mut openers = BTreeMap::new();
|
||||||
for (path, mime) in targets {
|
for (path, mime) in targets {
|
||||||
|
|
@ -252,7 +156,7 @@ impl Tasks {
|
||||||
pub fn precache_mime(&self, targets: &[File], mimetype: &HashMap<Url, String>) -> bool {
|
pub fn precache_mime(&self, targets: &[File], mimetype: &HashMap<Url, String>) -> bool {
|
||||||
let targets: Vec<_> = targets
|
let targets: Vec<_> = targets
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|f| f.is_file() && !mimetype.contains_key(&f.url))
|
.filter(|f| !f.is_dir() && !mimetype.contains_key(&f.url))
|
||||||
.map(|f| f.url())
|
.map(|f| f.url())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use crossterm::event::KeyEvent;
|
||||||
use tokio::sync::oneshot;
|
use tokio::sync::oneshot;
|
||||||
use yazi_config::{keymap::{Exec, Key, KeymapLayer}, BOOT};
|
use yazi_config::{keymap::{Exec, Key, KeymapLayer}, BOOT};
|
||||||
use yazi_core::{emit, files::FilesOp, input::InputMode, Ctx, Event};
|
use yazi_core::{emit, files::FilesOp, input::InputMode, Ctx, Event};
|
||||||
use yazi_shared::{expand_url, Term};
|
use yazi_shared::Term;
|
||||||
|
|
||||||
use crate::{Executor, Logs, Root, Signals};
|
use crate::{Executor, Logs, Root, Signals};
|
||||||
|
|
||||||
|
|
@ -121,11 +121,6 @@ impl App {
|
||||||
let manager = &mut self.cx.manager;
|
let manager = &mut self.cx.manager;
|
||||||
let tasks = &mut self.cx.tasks;
|
let tasks = &mut self.cx.tasks;
|
||||||
match event {
|
match event {
|
||||||
Event::Cd(url) => {
|
|
||||||
futures::executor::block_on(async {
|
|
||||||
manager.active_mut().cd(expand_url(url)).await;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Event::Refresh => {
|
Event::Refresh => {
|
||||||
manager.refresh();
|
manager.refresh();
|
||||||
}
|
}
|
||||||
|
|
@ -143,10 +138,8 @@ impl App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Pages(page) => {
|
Event::Pages(page) => {
|
||||||
if manager.current().page == page {
|
let targets = self.cx.manager.current().paginate(page);
|
||||||
let targets = self.cx.manager.current().paginate();
|
tasks.precache_mime(targets, &self.cx.manager.mimetype);
|
||||||
tasks.precache_mime(targets, &self.cx.manager.mimetype);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Event::Mimetype(mimes) => {
|
Event::Mimetype(mimes) => {
|
||||||
if manager.update_mimetype(mimes, tasks) {
|
if manager.update_mimetype(mimes, tasks) {
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,12 @@ impl<'a> Widget for Completion<'a> {
|
||||||
|
|
||||||
let input_area = self.cx.area(&self.cx.input.position);
|
let input_area = self.cx.area(&self.cx.input.position);
|
||||||
let mut area = self.cx.area(&Position::Sticky(
|
let mut area = self.cx.area(&Position::Sticky(
|
||||||
Rect { x: 1, y: 0, width: 20, height: items.len() as u16 + 2 },
|
Rect {
|
||||||
|
x: 1,
|
||||||
|
y: 0,
|
||||||
|
width: input_area.width.saturating_sub(2),
|
||||||
|
height: items.len() as u16 + 2,
|
||||||
|
},
|
||||||
input_area,
|
input_area,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use yazi_config::{keymap::{Control, Exec, Key, KeymapLayer}, KEYMAP};
|
use yazi_config::{keymap::{Control, Exec, Key, KeymapLayer}, KEYMAP};
|
||||||
use yazi_core::{emit, input::InputMode, tab::FinderCase, Ctx};
|
use yazi_core::{input::InputMode, Ctx};
|
||||||
use yazi_shared::{optional_bool, Url};
|
|
||||||
|
|
||||||
pub(super) struct Executor<'a> {
|
pub(super) struct Executor<'a> {
|
||||||
cx: &'a mut Ctx,
|
cx: &'a mut Ctx,
|
||||||
|
|
@ -71,260 +70,212 @@ impl<'a> Executor<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn manager(&mut self, exec: &Exec) -> bool {
|
fn manager(&mut self, exec: &Exec) -> bool {
|
||||||
match exec.cmd.as_str() {
|
macro_rules! on {
|
||||||
"escape" => self.cx.manager.active_mut().escape(exec),
|
(MANAGER, $name:ident $(,$args:expr)*) => {
|
||||||
"quit" => self.cx.manager.quit(&self.cx.tasks, exec.named.contains_key("no-cwd-file")),
|
if exec.cmd == stringify!($name) {
|
||||||
"close" => self.cx.manager.close(&self.cx.tasks),
|
return self.cx.manager.$name(exec, $($args),*);
|
||||||
"suspend" => self.cx.manager.suspend(),
|
}
|
||||||
|
};
|
||||||
|
(ACTIVE, $name:ident) => {
|
||||||
|
if exec.cmd == stringify!($name) {
|
||||||
|
return self.cx.manager.active_mut().$name(exec);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
(TABS, $name:ident) => {
|
||||||
|
if exec.cmd == concat!("tab_", stringify!($name)) {
|
||||||
|
return self.cx.manager.tabs.$name(exec);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Navigation
|
on!(ACTIVE, escape);
|
||||||
"arrow" => {
|
on!(MANAGER, quit, &self.cx.tasks);
|
||||||
let step = exec.args.first().and_then(|s| s.parse().ok()).unwrap_or_default();
|
on!(MANAGER, close, &self.cx.tasks);
|
||||||
self.cx.manager.active_mut().arrow(step)
|
on!(MANAGER, suspend);
|
||||||
}
|
|
||||||
"peek" => {
|
// Navigation
|
||||||
|
on!(ACTIVE, arrow);
|
||||||
|
on!(ACTIVE, leave);
|
||||||
|
on!(ACTIVE, enter);
|
||||||
|
on!(ACTIVE, back);
|
||||||
|
on!(ACTIVE, forward);
|
||||||
|
on!(ACTIVE, cd);
|
||||||
|
on!(ACTIVE, reveal);
|
||||||
|
|
||||||
|
// Selection
|
||||||
|
on!(ACTIVE, select);
|
||||||
|
on!(ACTIVE, select_all);
|
||||||
|
on!(ACTIVE, visual_mode);
|
||||||
|
|
||||||
|
// Operation
|
||||||
|
on!(MANAGER, open);
|
||||||
|
on!(MANAGER, yank);
|
||||||
|
on!(MANAGER, paste, &self.cx.tasks);
|
||||||
|
on!(MANAGER, link, &self.cx.tasks);
|
||||||
|
on!(MANAGER, remove, &self.cx.tasks);
|
||||||
|
on!(MANAGER, create);
|
||||||
|
on!(MANAGER, rename);
|
||||||
|
on!(ACTIVE, copy);
|
||||||
|
on!(ACTIVE, shell);
|
||||||
|
on!(ACTIVE, hidden);
|
||||||
|
on!(ACTIVE, linemode);
|
||||||
|
on!(ACTIVE, search);
|
||||||
|
on!(ACTIVE, jump);
|
||||||
|
|
||||||
|
// Find
|
||||||
|
on!(ACTIVE, find);
|
||||||
|
on!(ACTIVE, find_arrow);
|
||||||
|
|
||||||
|
// Sorting
|
||||||
|
on!(ACTIVE, sort);
|
||||||
|
|
||||||
|
// Tabs
|
||||||
|
on!(TABS, create);
|
||||||
|
on!(TABS, close);
|
||||||
|
on!(TABS, switch);
|
||||||
|
on!(TABS, swap);
|
||||||
|
|
||||||
|
match exec.cmd.as_bytes() {
|
||||||
|
b"peek" => {
|
||||||
let step = exec.args.first().and_then(|s| s.parse().ok()).unwrap_or(0);
|
let step = exec.args.first().and_then(|s| s.parse().ok()).unwrap_or(0);
|
||||||
self.cx.manager.active_mut().preview.arrow(step);
|
self.cx.manager.active_mut().preview.arrow(step);
|
||||||
self.cx.manager.peek(true, self.cx.image_layer())
|
self.cx.manager.peek(true, self.cx.image_layer())
|
||||||
}
|
}
|
||||||
"leave" => self.cx.manager.active_mut().leave(),
|
|
||||||
"enter" => self.cx.manager.active_mut().enter(),
|
|
||||||
"back" => self.cx.manager.active_mut().back(),
|
|
||||||
"forward" => self.cx.manager.active_mut().forward(),
|
|
||||||
"cd" => {
|
|
||||||
let url = exec.args.first().map(Url::from).unwrap_or_default();
|
|
||||||
if exec.named.contains_key("interactive") {
|
|
||||||
self.cx.manager.active_mut().cd_interactive(url)
|
|
||||||
} else {
|
|
||||||
emit!(Cd(url));
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Selection
|
|
||||||
"select" => {
|
|
||||||
let state = exec.named.get("state").cloned().unwrap_or("none".to_string());
|
|
||||||
self.cx.manager.active_mut().select(optional_bool(&state))
|
|
||||||
}
|
|
||||||
"select_all" => {
|
|
||||||
let state = exec.named.get("state").cloned().unwrap_or("none".to_string());
|
|
||||||
self.cx.manager.active_mut().select_all(optional_bool(&state))
|
|
||||||
}
|
|
||||||
"visual_mode" => self.cx.manager.active_mut().visual_mode(exec.named.contains_key("unset")),
|
|
||||||
|
|
||||||
// Operation
|
|
||||||
"open" => self.cx.manager.open(exec.named.contains_key("interactive")),
|
|
||||||
"yank" => self.cx.manager.yank(exec.named.contains_key("cut")),
|
|
||||||
"paste" => {
|
|
||||||
let dest = self.cx.manager.cwd();
|
|
||||||
let (cut, ref src) = self.cx.manager.yanked;
|
|
||||||
|
|
||||||
let force = exec.named.contains_key("force");
|
|
||||||
if cut {
|
|
||||||
self.cx.tasks.file_cut(src, dest, force)
|
|
||||||
} else {
|
|
||||||
self.cx.tasks.file_copy(src, dest, force)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"link" => {
|
|
||||||
let (cut, ref src) = self.cx.manager.yanked;
|
|
||||||
!cut
|
|
||||||
&& self.cx.tasks.file_link(
|
|
||||||
src,
|
|
||||||
self.cx.manager.cwd(),
|
|
||||||
exec.named.contains_key("relative"),
|
|
||||||
exec.named.contains_key("force"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
"remove" => {
|
|
||||||
let targets = self.cx.manager.selected().into_iter().map(|f| f.url()).collect();
|
|
||||||
let force = exec.named.contains_key("force");
|
|
||||||
let permanently = exec.named.contains_key("permanently");
|
|
||||||
self.cx.tasks.file_remove(targets, force, permanently)
|
|
||||||
}
|
|
||||||
"create" => self.cx.manager.create(exec.named.contains_key("force")),
|
|
||||||
"rename" => self.cx.manager.rename(exec.named.contains_key("force")),
|
|
||||||
"copy" => self.cx.manager.active().copy(exec.args.first().map(|s| s.as_str()).unwrap_or("")),
|
|
||||||
"shell" => self.cx.manager.active().shell(
|
|
||||||
exec.args.first().map(|e| e.as_str()).unwrap_or(""),
|
|
||||||
exec.named.contains_key("block"),
|
|
||||||
exec.named.contains_key("confirm"),
|
|
||||||
),
|
|
||||||
"hidden" => self.cx.manager.active_mut().hidden(exec),
|
|
||||||
"linemode" => self.cx.manager.active_mut().linemode(exec),
|
|
||||||
"search" => match exec.args.first().map(|s| s.as_str()).unwrap_or("") {
|
|
||||||
"rg" => self.cx.manager.active_mut().search(true),
|
|
||||||
"fd" => self.cx.manager.active_mut().search(false),
|
|
||||||
_ => self.cx.manager.active_mut().search_stop(),
|
|
||||||
},
|
|
||||||
"jump" => match exec.args.first().map(|s| s.as_str()).unwrap_or("") {
|
|
||||||
"fzf" => self.cx.manager.active_mut().jump(true),
|
|
||||||
"zoxide" => self.cx.manager.active_mut().jump(false),
|
|
||||||
_ => false,
|
|
||||||
},
|
|
||||||
|
|
||||||
// Find
|
|
||||||
"find" => {
|
|
||||||
let query = exec.args.first().map(|s| s.as_str());
|
|
||||||
let prev = exec.named.contains_key("previous");
|
|
||||||
let case = match (exec.named.contains_key("smart"), exec.named.contains_key("insensitive"))
|
|
||||||
{
|
|
||||||
(true, _) => FinderCase::Smart,
|
|
||||||
(_, false) => FinderCase::Sensitive,
|
|
||||||
(_, true) => FinderCase::Insensitive,
|
|
||||||
};
|
|
||||||
self.cx.manager.active_mut().find(query, prev, case)
|
|
||||||
}
|
|
||||||
"find_arrow" => self.cx.manager.active_mut().find_arrow(exec.named.contains_key("previous")),
|
|
||||||
|
|
||||||
// Sorting
|
|
||||||
"sort" => {
|
|
||||||
let b = self.cx.manager.active_mut().sort(exec);
|
|
||||||
self.cx.tasks.precache_size(&self.cx.manager.current().files);
|
|
||||||
b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tabs
|
|
||||||
"tab_create" => {
|
|
||||||
let path = if exec.named.contains_key("current") {
|
|
||||||
self.cx.manager.cwd().to_owned()
|
|
||||||
} else {
|
|
||||||
exec.args.first().map(Url::from).unwrap_or_else(|| Url::from("/"))
|
|
||||||
};
|
|
||||||
self.cx.manager.tabs.create(&path)
|
|
||||||
}
|
|
||||||
"tab_close" => {
|
|
||||||
let idx = exec.args.first().and_then(|i| i.parse().ok()).unwrap_or(0);
|
|
||||||
self.cx.manager.tabs.close(idx)
|
|
||||||
}
|
|
||||||
"tab_switch" => {
|
|
||||||
let step = exec.args.first().and_then(|s| s.parse().ok()).unwrap_or(0);
|
|
||||||
let rel = exec.named.contains_key("relative");
|
|
||||||
self.cx.manager.tabs.switch(step, rel)
|
|
||||||
}
|
|
||||||
"tab_swap" => {
|
|
||||||
let step = exec.args.first().and_then(|s| s.parse().ok()).unwrap_or(0);
|
|
||||||
self.cx.manager.tabs.swap(step)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tasks
|
// Tasks
|
||||||
"tasks_show" => self.cx.tasks.toggle(),
|
b"tasks_show" => self.cx.tasks.toggle(()),
|
||||||
|
|
||||||
// Help
|
// Help
|
||||||
"help" => self.cx.help.toggle(KeymapLayer::Manager),
|
b"help" => self.cx.help.toggle(KeymapLayer::Manager),
|
||||||
|
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tasks(&mut self, exec: &Exec) -> bool {
|
fn tasks(&mut self, exec: &Exec) -> bool {
|
||||||
|
macro_rules! on {
|
||||||
|
($name:ident) => {
|
||||||
|
if exec.cmd == stringify!($name) {
|
||||||
|
return self.cx.tasks.$name(exec);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($name:ident, $alias:literal) => {
|
||||||
|
if exec.cmd == $alias {
|
||||||
|
return self.cx.tasks.$name(exec);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
on!(toggle, "close");
|
||||||
|
on!(arrow);
|
||||||
|
on!(inspect);
|
||||||
|
on!(cancel);
|
||||||
|
|
||||||
match exec.cmd.as_str() {
|
match exec.cmd.as_str() {
|
||||||
"close" => self.cx.tasks.toggle(),
|
|
||||||
|
|
||||||
"arrow" => {
|
|
||||||
let step = exec.args.first().and_then(|s| s.parse().ok()).unwrap_or(0);
|
|
||||||
if step > 0 { self.cx.tasks.next() } else { self.cx.tasks.prev() }
|
|
||||||
}
|
|
||||||
|
|
||||||
"inspect" => self.cx.tasks.inspect(),
|
|
||||||
"cancel" => self.cx.tasks.cancel(),
|
|
||||||
|
|
||||||
"help" => self.cx.help.toggle(KeymapLayer::Tasks),
|
"help" => self.cx.help.toggle(KeymapLayer::Tasks),
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select(&mut self, exec: &Exec) -> bool {
|
fn select(&mut self, exec: &Exec) -> bool {
|
||||||
match exec.cmd.as_str() {
|
macro_rules! on {
|
||||||
"close" => self.cx.select.close(exec.named.contains_key("submit")),
|
($name:ident) => {
|
||||||
|
if exec.cmd == stringify!($name) {
|
||||||
"arrow" => {
|
return self.cx.select.$name(exec);
|
||||||
let step: isize = exec.args.first().and_then(|s| s.parse().ok()).unwrap_or(0);
|
|
||||||
if step > 0 {
|
|
||||||
self.cx.select.next(step as usize)
|
|
||||||
} else {
|
|
||||||
self.cx.select.prev(step.unsigned_abs())
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
on!(close);
|
||||||
|
on!(arrow);
|
||||||
|
|
||||||
|
match exec.cmd.as_str() {
|
||||||
"help" => self.cx.help.toggle(KeymapLayer::Select),
|
"help" => self.cx.help.toggle(KeymapLayer::Select),
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn input(&mut self, exec: &Exec) -> bool {
|
fn input(&mut self, exec: &Exec) -> bool {
|
||||||
match exec.cmd.as_str() {
|
macro_rules! on {
|
||||||
"close" => return self.cx.input.close(exec.named.contains_key("submit")),
|
($name:ident) => {
|
||||||
"escape" => return self.cx.input.escape(),
|
if exec.cmd == stringify!($name) {
|
||||||
|
return self.cx.input.$name(exec);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($name:ident, $alias:literal) => {
|
||||||
|
if exec.cmd == $alias {
|
||||||
|
return self.cx.input.$name(exec);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
"move" => {
|
on!(close);
|
||||||
let step = exec.args.first().and_then(|s| s.parse().ok()).unwrap_or(0);
|
on!(escape);
|
||||||
let in_operating = exec.named.contains_key("in-operating");
|
on!(move_, "move");
|
||||||
return if in_operating {
|
|
||||||
self.cx.input.move_in_operating(step)
|
|
||||||
} else {
|
|
||||||
self.cx.input.move_(step)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
"complete" => {
|
if exec.cmd.as_str() == "complete" {
|
||||||
return if exec.args.is_empty() {
|
return if exec.args.is_empty() {
|
||||||
self.cx.completion.trigger(exec)
|
self.cx.completion.trigger(exec)
|
||||||
} else {
|
} else {
|
||||||
self.cx.input.complete(exec)
|
self.cx.input.complete(exec)
|
||||||
};
|
};
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.cx.input.mode() {
|
match self.cx.input.mode() {
|
||||||
InputMode::Normal => match exec.cmd.as_str() {
|
InputMode::Normal => {
|
||||||
"insert" => self.cx.input.insert(exec.named.contains_key("append")),
|
on!(insert);
|
||||||
"visual" => self.cx.input.visual(),
|
on!(visual);
|
||||||
|
|
||||||
"backward" => self.cx.input.backward(),
|
on!(backward);
|
||||||
"forward" => self.cx.input.forward(exec.named.contains_key("end-of-word")),
|
on!(forward);
|
||||||
"delete" => {
|
on!(delete);
|
||||||
self.cx.input.delete(exec.named.contains_key("cut"), exec.named.contains_key("insert"))
|
|
||||||
|
on!(yank);
|
||||||
|
on!(paste);
|
||||||
|
|
||||||
|
on!(undo);
|
||||||
|
on!(redo);
|
||||||
|
|
||||||
|
match exec.cmd.as_str() {
|
||||||
|
"help" => self.cx.help.toggle(KeymapLayer::Input),
|
||||||
|
_ => false,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
"yank" => self.cx.input.yank(),
|
|
||||||
"paste" => self.cx.input.paste(exec.named.contains_key("before")),
|
|
||||||
|
|
||||||
"undo" => self.cx.input.undo(),
|
|
||||||
"redo" => self.cx.input.redo(),
|
|
||||||
|
|
||||||
"help" => self.cx.help.toggle(KeymapLayer::Input),
|
|
||||||
_ => false,
|
|
||||||
},
|
|
||||||
InputMode::Insert => false,
|
InputMode::Insert => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn help(&mut self, exec: &Exec) -> bool {
|
fn help(&mut self, exec: &Exec) -> bool {
|
||||||
|
macro_rules! on {
|
||||||
|
($name:ident) => {
|
||||||
|
if exec.cmd == stringify!($name) {
|
||||||
|
return self.cx.help.$name(exec);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
on!(escape);
|
||||||
|
on!(arrow);
|
||||||
|
on!(filter);
|
||||||
|
|
||||||
match exec.cmd.as_str() {
|
match exec.cmd.as_str() {
|
||||||
"close" => self.cx.help.toggle(KeymapLayer::Help),
|
"close" => self.cx.help.toggle(KeymapLayer::Help),
|
||||||
"escape" => self.cx.help.escape(),
|
|
||||||
|
|
||||||
"arrow" => {
|
|
||||||
let step = exec.args.first().and_then(|s| s.parse().ok()).unwrap_or(0);
|
|
||||||
self.cx.help.arrow(step)
|
|
||||||
}
|
|
||||||
|
|
||||||
"filter" => self.cx.help.filter(),
|
|
||||||
|
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn completion(&mut self, exec: &Exec) -> bool {
|
fn completion(&mut self, exec: &Exec) -> bool {
|
||||||
|
macro_rules! on {
|
||||||
|
($name:ident) => {
|
||||||
|
if exec.cmd == stringify!($name) {
|
||||||
|
return self.cx.completion.$name(exec);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
on!(trigger);
|
||||||
|
on!(show);
|
||||||
|
on!(close);
|
||||||
|
on!(arrow);
|
||||||
|
|
||||||
match exec.cmd.as_str() {
|
match exec.cmd.as_str() {
|
||||||
"trigger" => self.cx.completion.trigger(exec),
|
|
||||||
"show" => self.cx.completion.show(exec),
|
|
||||||
"close" => self.cx.completion.close(exec),
|
|
||||||
|
|
||||||
"arrow" => self.cx.completion.arrow(exec),
|
|
||||||
|
|
||||||
"help" => self.cx.help.toggle(KeymapLayer::Completion),
|
"help" => self.cx.help.toggle(KeymapLayer::Completion),
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,6 @@ function ui.highlight_ranges(s, ranges)
|
||||||
if r[1] > last then
|
if r[1] > last then
|
||||||
spans[#spans + 1] = ui.Span(s:sub(last + 1, r[1]))
|
spans[#spans + 1] = ui.Span(s:sub(last + 1, r[1]))
|
||||||
end
|
end
|
||||||
-- TODO: use a customable style
|
|
||||||
spans[#spans + 1] = ui.Span(s:sub(r[1] + 1, r[2])):style(THEME.manager.find_keyword)
|
spans[#spans + 1] = ui.Span(s:sub(r[1] + 1, r[2])):style(THEME.manager.find_keyword)
|
||||||
last = r[2]
|
last = r[2]
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,8 @@ impl UserData for File {
|
||||||
fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) {
|
fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) {
|
||||||
fields.add_field_method_get("url", |_, me| Ok(Url::from(&me.0.url)));
|
fields.add_field_method_get("url", |_, me| Ok(Url::from(&me.0.url)));
|
||||||
fields.add_field_method_get("link_to", |_, me| Ok(me.0.link_to().map(Url::from)));
|
fields.add_field_method_get("link_to", |_, me| Ok(me.0.link_to().map(Url::from)));
|
||||||
fields.add_field_method_get("is_link", |_, me| Ok(me.0.is_link));
|
fields.add_field_method_get("is_link", |_, me| Ok(me.0.is_link()));
|
||||||
fields.add_field_method_get("is_hidden", |_, me| Ok(me.0.is_hidden));
|
fields.add_field_method_get("is_hidden", |_, me| Ok(me.0.is_hidden()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -50,36 +50,37 @@ impl Files {
|
||||||
LUA.register_userdata_type::<yazi_core::files::File>(|reg| {
|
LUA.register_userdata_type::<yazi_core::files::File>(|reg| {
|
||||||
reg.add_field_method_get("url", |_, me| Ok(Url::from(&me.url)));
|
reg.add_field_method_get("url", |_, me| Ok(Url::from(&me.url)));
|
||||||
reg.add_field_method_get("link_to", |_, me| Ok(me.link_to().map(Url::from)));
|
reg.add_field_method_get("link_to", |_, me| Ok(me.link_to().map(Url::from)));
|
||||||
reg.add_field_method_get("is_link", |_, me| Ok(me.is_link));
|
reg.add_field_method_get("is_link", |_, me| Ok(me.is_link()));
|
||||||
reg.add_field_method_get("is_hidden", |_, me| Ok(me.is_hidden));
|
reg.add_field_method_get("is_hidden", |_, me| Ok(me.is_hidden()));
|
||||||
|
|
||||||
// Metadata
|
// Metadata
|
||||||
reg.add_field_method_get("is_file", |_, me| Ok(me.is_file()));
|
|
||||||
reg.add_field_method_get("is_dir", |_, me| Ok(me.is_dir()));
|
reg.add_field_method_get("is_dir", |_, me| Ok(me.is_dir()));
|
||||||
reg.add_field_method_get("is_symlink", |_, me| Ok(me.meta.is_symlink()));
|
reg.add_field_method_get("is_symlink", |_, me| Ok(me.is_link()));
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
{
|
{
|
||||||
use std::os::unix::prelude::FileTypeExt;
|
reg.add_field_method_get("is_block_device", |_, me| Ok(me.is_block_device()));
|
||||||
reg.add_field_method_get("is_block_device", |_, me| {
|
reg.add_field_method_get("is_char_device", |_, me| Ok(me.is_char_device()));
|
||||||
Ok(me.meta.file_type().is_block_device())
|
reg.add_field_method_get("is_fifo", |_, me| Ok(me.is_fifo()));
|
||||||
});
|
reg.add_field_method_get("is_socket", |_, me| Ok(me.is_socket()));
|
||||||
reg
|
|
||||||
.add_field_method_get("is_char_device", |_, me| Ok(me.meta.file_type().is_char_device()));
|
|
||||||
reg.add_field_method_get("is_fifo", |_, me| Ok(me.meta.file_type().is_fifo()));
|
|
||||||
reg.add_field_method_get("is_socket", |_, me| Ok(me.meta.file_type().is_socket()));
|
|
||||||
}
|
}
|
||||||
reg.add_field_method_get("length", |_, me| Ok(me.meta.len()));
|
reg.add_field_method_get("length", |_, me| Ok(me.len));
|
||||||
reg.add_field_method_get("created", |_, me| {
|
reg.add_field_method_get("created", |_, me| {
|
||||||
Ok(me.meta.created()?.duration_since(UNIX_EPOCH).map(|d| d.as_secs_f64()).ok())
|
Ok(me.created.and_then(|t| t.duration_since(UNIX_EPOCH).map(|d| d.as_secs_f64()).ok()))
|
||||||
});
|
});
|
||||||
reg.add_field_method_get("modified", |_, me| {
|
reg.add_field_method_get("modified", |_, me| {
|
||||||
Ok(me.meta.modified()?.duration_since(UNIX_EPOCH).map(|d| d.as_secs_f64()).ok())
|
Ok(me.modified.and_then(|t| t.duration_since(UNIX_EPOCH).map(|d| d.as_secs_f64()).ok()))
|
||||||
});
|
});
|
||||||
reg.add_field_method_get("accessed", |_, me| {
|
reg.add_field_method_get("accessed", |_, me| {
|
||||||
Ok(me.meta.accessed()?.duration_since(UNIX_EPOCH).map(|d| d.as_secs_f64()).ok())
|
Ok(me.accessed.and_then(|t| t.duration_since(UNIX_EPOCH).map(|d| d.as_secs_f64()).ok()))
|
||||||
|
});
|
||||||
|
reg.add_method("permissions", |_, me, ()| {
|
||||||
|
Ok(
|
||||||
|
#[cfg(unix)]
|
||||||
|
Some(yazi_shared::permissions(me.permissions)),
|
||||||
|
#[cfg(windows)]
|
||||||
|
None::<String>,
|
||||||
|
)
|
||||||
});
|
});
|
||||||
reg
|
|
||||||
.add_method("permissions", |_, me, ()| Ok(yazi_shared::permissions(me.meta.permissions())));
|
|
||||||
|
|
||||||
// Extension
|
// Extension
|
||||||
reg.add_field_method_get("name", |_, me| {
|
reg.add_field_method_get("name", |_, me| {
|
||||||
|
|
@ -88,7 +89,7 @@ impl Files {
|
||||||
reg.add_function("size", |_, me: AnyUserData| {
|
reg.add_function("size", |_, me: AnyUserData| {
|
||||||
let file = me.borrow::<yazi_core::files::File>()?;
|
let file = me.borrow::<yazi_core::files::File>()?;
|
||||||
if !file.is_dir() {
|
if !file.is_dir() {
|
||||||
return Ok(Some(file.meta.len()));
|
return Ok(Some(file.len));
|
||||||
}
|
}
|
||||||
|
|
||||||
let folder = me.named_user_value::<UserDataRef<yazi_core::tab::Folder>>("folder")?;
|
let folder = me.named_user_value::<UserDataRef<yazi_core::tab::Folder>>("folder")?;
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,13 @@ homepage = "https://yazi-rs.github.io"
|
||||||
repository = "https://github.com/sxyazi/yazi"
|
repository = "https://github.com/sxyazi/yazi"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "^1"
|
anyhow = "^1"
|
||||||
crossterm = "^0"
|
bitflags = "^2"
|
||||||
futures = "^0"
|
crossterm = "^0"
|
||||||
libc = "^0"
|
futures = "^0"
|
||||||
parking_lot = "^0"
|
libc = "^0"
|
||||||
ratatui = "^0"
|
parking_lot = "^0"
|
||||||
regex = "^1"
|
percent-encoding = "^2"
|
||||||
tokio = { version = "^1", features = [ "parking_lot", "macros", "rt-multi-thread", "sync", "time", "fs" ] }
|
ratatui = "^0"
|
||||||
|
regex = "^1"
|
||||||
|
tokio = { version = "^1", features = [ "parking_lot", "macros", "rt-multi-thread", "sync", "time", "fs" ] }
|
||||||
|
|
|
||||||
108
yazi-shared/src/cha.rs
Normal file
108
yazi-shared/src/cha.rs
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
use std::{fs::Metadata, time::SystemTime};
|
||||||
|
|
||||||
|
use bitflags::bitflags;
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
|
pub struct ChaMeta: u8 {
|
||||||
|
const DIR = 0b00000001;
|
||||||
|
|
||||||
|
const HIDDEN = 0b00000010;
|
||||||
|
const LINK = 0b00000100;
|
||||||
|
const BAD_LINK = 0b00001000;
|
||||||
|
|
||||||
|
const BLOCK_DEVICE = 0b00010000;
|
||||||
|
const CHAR_DEVICE = 0b00100000;
|
||||||
|
const FIFO = 0b01000000;
|
||||||
|
const SOCKET = 0b10000000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
|
pub struct Cha {
|
||||||
|
pub meta: ChaMeta,
|
||||||
|
pub len: u64,
|
||||||
|
pub accessed: Option<SystemTime>,
|
||||||
|
pub created: Option<SystemTime>,
|
||||||
|
pub modified: Option<SystemTime>,
|
||||||
|
#[cfg(unix)]
|
||||||
|
pub permissions: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Metadata> for Cha {
|
||||||
|
fn from(m: Metadata) -> Self {
|
||||||
|
let mut cm = ChaMeta::empty();
|
||||||
|
if m.is_dir() {
|
||||||
|
cm |= ChaMeta::DIR;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
use std::os::unix::prelude::FileTypeExt;
|
||||||
|
if m.file_type().is_block_device() {
|
||||||
|
cm |= ChaMeta::BLOCK_DEVICE;
|
||||||
|
}
|
||||||
|
if m.file_type().is_char_device() {
|
||||||
|
cm |= ChaMeta::CHAR_DEVICE;
|
||||||
|
}
|
||||||
|
if m.file_type().is_fifo() {
|
||||||
|
cm |= ChaMeta::FIFO;
|
||||||
|
}
|
||||||
|
if m.file_type().is_socket() {
|
||||||
|
cm |= ChaMeta::SOCKET;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Self {
|
||||||
|
meta: cm,
|
||||||
|
len: m.len(),
|
||||||
|
accessed: m.accessed().ok(),
|
||||||
|
created: m.created().ok(),
|
||||||
|
modified: m.modified().ok(),
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
permissions: {
|
||||||
|
use std::os::unix::prelude::PermissionsExt;
|
||||||
|
m.permissions().mode()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cha {
|
||||||
|
#[inline]
|
||||||
|
pub fn with_meta(mut self, meta: ChaMeta) -> Self {
|
||||||
|
self.meta |= meta;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cha {
|
||||||
|
#[inline]
|
||||||
|
pub fn is_dir(self) -> bool { self.meta.contains(ChaMeta::DIR) }
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn is_hidden(self) -> bool { self.meta.contains(ChaMeta::HIDDEN) }
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn is_link(self) -> bool { self.meta.contains(ChaMeta::LINK) }
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn is_bad_link(self) -> bool { self.meta.contains(ChaMeta::BAD_LINK) }
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
#[inline]
|
||||||
|
pub fn is_block_device(self) -> bool { self.meta.contains(ChaMeta::BLOCK_DEVICE) }
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
#[inline]
|
||||||
|
pub fn is_char_device(self) -> bool { self.meta.contains(ChaMeta::CHAR_DEVICE) }
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
#[inline]
|
||||||
|
pub fn is_fifo(self) -> bool { self.meta.contains(ChaMeta::FIFO) }
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
#[inline]
|
||||||
|
pub fn is_socket(self) -> bool { self.meta.contains(ChaMeta::SOCKET) }
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
use std::{borrow::Cow, env, ffi::OsString, path::{Component, Path, PathBuf}};
|
use std::{borrow::Cow, env, ffi::OsString, path::{Component, Path, PathBuf, MAIN_SEPARATOR}};
|
||||||
|
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
|
|
||||||
use crate::Url;
|
use crate::Url;
|
||||||
|
|
||||||
pub fn expand_path(p: impl AsRef<Path>) -> PathBuf {
|
fn _expand_path(p: &Path) -> PathBuf {
|
||||||
// ${HOME} or $HOME
|
// ${HOME} or $HOME
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
let re = regex::Regex::new(r"\$(?:\{([^}]+)\}|([a-zA-Z\d_]+))").unwrap();
|
let re = regex::Regex::new(r"\$(?:\{([^}]+)\}|([a-zA-Z\d_]+))").unwrap();
|
||||||
|
|
@ -13,22 +13,20 @@ pub fn expand_path(p: impl AsRef<Path>) -> PathBuf {
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
let re = regex::Regex::new(r"%([^%]+)%").unwrap();
|
let re = regex::Regex::new(r"%([^%]+)%").unwrap();
|
||||||
|
|
||||||
let s = p.as_ref().to_string_lossy();
|
let s = p.to_string_lossy();
|
||||||
let s = re.replace_all(&s, |caps: ®ex::Captures| {
|
let s = re.replace_all(&s, |caps: ®ex::Captures| {
|
||||||
let name = caps.get(2).or_else(|| caps.get(1)).unwrap();
|
let name = caps.get(2).or_else(|| caps.get(1)).unwrap();
|
||||||
env::var(name.as_str()).unwrap_or_else(|_| caps.get(0).unwrap().as_str().to_owned())
|
env::var(name.as_str()).unwrap_or_else(|_| caps.get(0).unwrap().as_str().to_owned())
|
||||||
});
|
});
|
||||||
|
|
||||||
let p = Path::new(s.as_ref());
|
let p = Path::new(s.as_ref());
|
||||||
if let Ok(p) = p.strip_prefix("~") {
|
if let Ok(rest) = p.strip_prefix("~") {
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
if let Some(home) = env::var_os("HOME") {
|
let home = env::var_os("HOME");
|
||||||
return Path::new(&home).join(p);
|
|
||||||
}
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
if let Some(home) = env::var_os("USERPROFILE") {
|
let home = env::var_os("USERPROFILE");
|
||||||
return Path::new(&home).join(p);
|
|
||||||
}
|
return if let Some(p) = home { PathBuf::from(p).join(rest) } else { rest.to_path_buf() };
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.is_absolute() {
|
if p.is_absolute() {
|
||||||
|
|
@ -37,12 +35,36 @@ pub fn expand_path(p: impl AsRef<Path>) -> PathBuf {
|
||||||
env::current_dir().map_or_else(|_| p.to_path_buf(), |c| c.join(p))
|
env::current_dir().map_or_else(|_| p.to_path_buf(), |c| c.join(p))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn expand_path(p: impl AsRef<Path>) -> PathBuf { _expand_path(p.as_ref()) }
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn expand_url(mut u: Url) -> Url {
|
pub fn expand_url(mut u: Url) -> Url {
|
||||||
u.set_path(expand_path(&u));
|
u.set_path(_expand_path(&u));
|
||||||
u
|
u
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn ends_with_slash(p: &Path) -> bool {
|
||||||
|
// TODO: uncomment this when Rust 1.74 is released
|
||||||
|
// let b = p.as_os_str().as_encoded_bytes();
|
||||||
|
// if let [.., last] = b { *last == MAIN_SEPARATOR as u8 } else { false }
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
use std::os::unix::ffi::OsStrExt;
|
||||||
|
let b = p.as_os_str().as_bytes();
|
||||||
|
if let [.., last] = b { *last == MAIN_SEPARATOR as u8 } else { false }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
let s = p.to_string_lossy();
|
||||||
|
let b = s.as_bytes();
|
||||||
|
if let [.., last] = b { *last == MAIN_SEPARATOR as u8 } else { false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn unique_path(mut p: Url) -> Url {
|
pub async fn unique_path(mut p: Url) -> Url {
|
||||||
let Some(stem) = p.file_stem().map(|s| s.to_owned()) else {
|
let Some(stem) = p.file_stem().map(|s| s.to_owned()) else {
|
||||||
return p;
|
return p;
|
||||||
|
|
@ -119,15 +141,6 @@ pub fn path_relative_to<'a>(path: &'a Path, root: &Path) -> Cow<'a, Path> {
|
||||||
Cow::from(buf)
|
Cow::from(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn optional_bool(s: &str) -> Option<bool> {
|
|
||||||
match s {
|
|
||||||
"true" => Some(true),
|
|
||||||
"false" => Some(false),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::{borrow::Cow, path::Path};
|
use std::{borrow::Cow, path::Path};
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{collections::VecDeque, fs::Permissions, path::{Path, PathBuf}};
|
use std::{collections::VecDeque, path::{Path, PathBuf}};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use tokio::{fs, io, select, sync::{mpsc, oneshot}, time};
|
use tokio::{fs, io, select, sync::{mpsc, oneshot}, time};
|
||||||
|
|
@ -91,29 +91,14 @@ pub fn copy_with_progress(from: &Path, to: &Path) -> mpsc::Receiver<Result<u64,
|
||||||
rx
|
rx
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert a file mode to a string representation
|
|
||||||
#[cfg(windows)]
|
|
||||||
#[allow(clippy::collapsible_else_if)]
|
|
||||||
pub fn permissions(_: Permissions) -> Option<String> { None }
|
|
||||||
|
|
||||||
// Convert a file mode to a string representation
|
// Convert a file mode to a string representation
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
#[allow(clippy::collapsible_else_if)]
|
#[allow(clippy::collapsible_else_if)]
|
||||||
pub fn permissions(permissions: Permissions) -> Option<String> {
|
pub fn permissions(mode: u32) -> String {
|
||||||
use std::os::unix::prelude::PermissionsExt;
|
use libc::{mode_t, S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFSOCK, S_IRGRP, S_IROTH, S_IRUSR, S_ISGID, S_ISUID, S_ISVTX, S_IWGRP, S_IWOTH, S_IWUSR, S_IXGRP, S_IXOTH, S_IXUSR};
|
||||||
|
|
||||||
use libc::{S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFSOCK, S_IRGRP, S_IROTH, S_IRUSR, S_ISGID, S_ISUID, S_ISVTX, S_IWGRP, S_IWOTH, S_IWUSR, S_IXGRP, S_IXOTH, S_IXUSR};
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
let m = permissions.mode() as u16;
|
|
||||||
#[cfg(target_os = "freebsd")]
|
|
||||||
let m = permissions.mode() as u16;
|
|
||||||
#[cfg(target_os = "netbsd")]
|
|
||||||
let m = permissions.mode();
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
let m = permissions.mode();
|
|
||||||
|
|
||||||
let mut s = String::with_capacity(10);
|
let mut s = String::with_capacity(10);
|
||||||
|
let m = mode as mode_t;
|
||||||
|
|
||||||
// File type
|
// File type
|
||||||
s.push(match m & S_IFMT {
|
s.push(match m & S_IFMT {
|
||||||
|
|
@ -153,7 +138,7 @@ pub fn permissions(permissions: Permissions) -> Option<String> {
|
||||||
if m & S_ISVTX != 0 { 'T' } else { '-' }
|
if m & S_ISVTX != 0 { 'T' } else { '-' }
|
||||||
});
|
});
|
||||||
|
|
||||||
Some(s)
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the max common root of a list of files
|
// Find the max common root of a list of files
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#![allow(clippy::option_map_unit_fn)]
|
#![allow(clippy::option_map_unit_fn)]
|
||||||
|
|
||||||
|
mod cha;
|
||||||
mod chars;
|
mod chars;
|
||||||
mod debounce;
|
mod debounce;
|
||||||
mod defer;
|
mod defer;
|
||||||
|
|
@ -14,6 +15,7 @@ mod throttle;
|
||||||
mod time;
|
mod time;
|
||||||
mod url;
|
mod url;
|
||||||
|
|
||||||
|
pub use cha::*;
|
||||||
pub use chars::*;
|
pub use chars::*;
|
||||||
pub use debounce::*;
|
pub use debounce::*;
|
||||||
pub use defer::*;
|
pub use defer::*;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
use std::{ffi::{OsStr, OsString}, fmt::{Debug, Formatter}, ops::{Deref, DerefMut}, path::{Path, PathBuf}};
|
use std::{ffi::{OsStr, OsString}, fmt::{Debug, Formatter}, ops::{Deref, DerefMut}, path::{Path, PathBuf}};
|
||||||
|
|
||||||
|
use percent_encoding::{percent_decode_str, percent_encode, AsciiSet, CONTROLS};
|
||||||
|
|
||||||
|
const ENCODE_SET: &AsciiSet = &CONTROLS.add(b'#');
|
||||||
|
|
||||||
#[derive(Clone, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
#[derive(Clone, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||||
pub struct Url {
|
pub struct Url {
|
||||||
scheme: UrlScheme,
|
scheme: UrlScheme,
|
||||||
|
|
@ -42,15 +46,41 @@ impl From<&Path> for Url {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<String> for Url {
|
impl From<String> for Url {
|
||||||
fn from(path: String) -> Self { Self::from(PathBuf::from(path)) }
|
fn from(path: String) -> Self { Self::from(path.as_str()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&String> for Url {
|
impl From<&String> for Url {
|
||||||
fn from(path: &String) -> Self { Self::from(PathBuf::from(path)) }
|
fn from(path: &String) -> Self { Self::from(path.as_str()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&str> for Url {
|
impl From<&str> for Url {
|
||||||
fn from(path: &str) -> Self { Self::from(PathBuf::from(path)) }
|
fn from(mut path: &str) -> Self {
|
||||||
|
let mut url = Url::default();
|
||||||
|
match path.split_once("://").map(|(a, b)| (UrlScheme::from(a), b)) {
|
||||||
|
None => {
|
||||||
|
url.path = PathBuf::from(path);
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
Some((UrlScheme::Regular, b)) => {
|
||||||
|
url.path = PathBuf::from(b);
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
Some((a, b)) => {
|
||||||
|
url.scheme = a;
|
||||||
|
path = b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match path.split_once('#') {
|
||||||
|
None => {
|
||||||
|
url.path = percent_decode_str(path).decode_utf8_lossy().into_owned().into();
|
||||||
|
}
|
||||||
|
Some((a, b)) => {
|
||||||
|
url.path = percent_decode_str(a).decode_utf8_lossy().into_owned().into();
|
||||||
|
url.frag = Some(b.to_string()).filter(|s| !s.is_empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
url
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsRef<Url> for Url {
|
impl AsRef<Url> for Url {
|
||||||
|
|
@ -65,6 +95,32 @@ impl AsRef<OsStr> for Url {
|
||||||
fn as_ref(&self) -> &OsStr { self.path.as_os_str() }
|
fn as_ref(&self) -> &OsStr { self.path.as_os_str() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ToString for Url {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
if self.scheme == UrlScheme::Regular {
|
||||||
|
return self.path.to_string_lossy().to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
let scheme = match self.scheme {
|
||||||
|
UrlScheme::Regular => unreachable!(),
|
||||||
|
UrlScheme::Search => "search://",
|
||||||
|
UrlScheme::Archive => "archive://",
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
let path = {
|
||||||
|
use std::os::unix::ffi::OsStrExt;
|
||||||
|
percent_encode(self.path.as_os_str().as_bytes(), ENCODE_SET)
|
||||||
|
};
|
||||||
|
#[cfg(windows)]
|
||||||
|
let path = percent_encode(self.path.to_string_lossy().as_bytes(), ENCODE_SET).to_string();
|
||||||
|
|
||||||
|
let frag = self.frag.as_ref().map(|s| format!("#{s}")).unwrap_or_default();
|
||||||
|
|
||||||
|
format!("{scheme}{path}{frag}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Url {
|
impl Url {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn join(&self, path: impl AsRef<Path>) -> Self {
|
pub fn join(&self, path: impl AsRef<Path>) -> Self {
|
||||||
|
|
@ -95,6 +151,11 @@ impl Url {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn into_os_string(self) -> OsString { self.path.into_os_string() }
|
pub fn into_os_string(self) -> OsString { self.path.into_os_string() }
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn is_hidden(&self) -> bool {
|
||||||
|
self.file_name().map_or(false, |s| s.to_string_lossy().starts_with('.'))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Url {
|
impl Url {
|
||||||
|
|
@ -144,3 +205,13 @@ impl Url {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn frag(&self) -> Option<&str> { self.frag.as_deref() }
|
pub fn frag(&self) -> Option<&str> { self.frag.as_deref() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<&str> for UrlScheme {
|
||||||
|
fn from(value: &str) -> Self {
|
||||||
|
match value {
|
||||||
|
"search" => UrlScheme::Search,
|
||||||
|
"archive" => UrlScheme::Archive,
|
||||||
|
_ => UrlScheme::Regular,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue