mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
..
This commit is contained in:
parent
0932752234
commit
8100598224
11 changed files with 117 additions and 35 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -2565,6 +2565,7 @@ dependencies = [
|
||||||
"futures",
|
"futures",
|
||||||
"libc",
|
"libc",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
|
"percent-encoding",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
"regex",
|
"regex",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
};
|
};
|
||||||
|
|
|
||||||
8
yazi-core/src/external/fzf.rs
vendored
8
yazi-core/src/external/fzf.rs
vendored
|
|
@ -1,4 +1,4 @@
|
||||||
use std::process::Stdio;
|
use std::{path::Path, process::Stdio};
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
|
@ -15,8 +15,8 @@ pub async fn fzf(opt: FzfOpt) -> Result<Url> {
|
||||||
let output = child.wait_with_output().await?;
|
let output = child.wait_with_output().await?;
|
||||||
let selected = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
let selected = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||||
|
|
||||||
if !selected.is_empty() {
|
if selected.is_empty() {
|
||||||
return Ok(Url::from(selected));
|
bail!("No match")
|
||||||
}
|
}
|
||||||
bail!("No match")
|
return Ok(Url::from(Path::new(&opt.cwd).join(selected)));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,14 @@ use std::{mem, time::Duration};
|
||||||
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 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, files::{File, FilesOp}, input::InputOpt, tab::Tab};
|
||||||
|
|
||||||
impl Tab {
|
impl Tab {
|
||||||
// TODO: change to sync, and remove `Event::Cd`
|
|
||||||
pub fn cd(&mut self, mut target: Url) -> bool {
|
pub fn cd(&mut self, mut target: Url) -> bool {
|
||||||
let mut hovered = None;
|
let mut hovered = None;
|
||||||
if let (false, Some(parent)) = (target.was_dir(), target.parent_url()) {
|
if let (false, Some(parent)) = (target.pop_dir(), target.parent_url()) {
|
||||||
emit!(Files(FilesOp::Creating(parent.clone(), File::from_dummy(target.clone()).into_map())));
|
emit!(Files(FilesOp::Creating(parent.clone(), File::from_dummy(target.clone()).into_map())));
|
||||||
hovered = Some(target);
|
hovered = Some(target);
|
||||||
target = parent;
|
target = parent;
|
||||||
|
|
@ -68,7 +67,10 @@ 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())));
|
emit!(Call(
|
||||||
|
Exec::call("cd", vec![expand_path(s).to_string_lossy().to_string()]).vec(),
|
||||||
|
KeymapLayer::Manager
|
||||||
|
));
|
||||||
}
|
}
|
||||||
Err(InputError::Completed(before, ticket)) => {
|
Err(InputError::Completed(before, ticket)) => {
|
||||||
emit!(Call(
|
emit!(Call(
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
use yazi_config::keymap::{Exec, KeymapLayer};
|
||||||
use yazi_shared::Defer;
|
use yazi_shared::Defer;
|
||||||
|
|
||||||
use crate::{emit, external::{self, FzfOpt, ZoxideOpt}, tab::Tab, Event, BLOCKER};
|
use crate::{emit, external::{self, FzfOpt, ZoxideOpt}, tab::Tab, Event, BLOCKER};
|
||||||
|
|
@ -17,7 +18,7 @@ impl Tab {
|
||||||
external::zoxide(ZoxideOpt { cwd }).await
|
external::zoxide(ZoxideOpt { cwd }).await
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
emit!(Cd(url));
|
emit!(Call(Exec::call("cd", vec![url.to_string()]).vec(), KeymapLayer::Manager));
|
||||||
Ok::<(), anyhow::Error>(())
|
Ok::<(), anyhow::Error>(())
|
||||||
});
|
});
|
||||||
false
|
false
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ 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};
|
||||||
|
|
||||||
|
|
@ -34,7 +35,10 @@ 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().into_dir().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)));
|
||||||
|
|
|
||||||
|
|
@ -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,9 +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) => {
|
|
||||||
manager.active_mut().cd(expand_url(url));
|
|
||||||
}
|
|
||||||
Event::Refresh => {
|
Event::Refresh => {
|
||||||
manager.refresh();
|
manager.refresh();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use yazi_config::{keymap::{Control, Exec, Key, KeymapLayer}, KEYMAP};
|
use yazi_config::{keymap::{Control, Exec, Key, KeymapLayer}, KEYMAP};
|
||||||
use yazi_core::{input::InputMode, tab::FinderCase, Ctx};
|
use yazi_core::{input::InputMode, tab::FinderCase, Ctx};
|
||||||
use yazi_shared::{optional_bool, Url};
|
use yazi_shared::{expand_url, optional_bool, Url};
|
||||||
|
|
||||||
pub(super) struct Executor<'a> {
|
pub(super) struct Executor<'a> {
|
||||||
cx: &'a mut Ctx,
|
cx: &'a mut Ctx,
|
||||||
|
|
@ -96,7 +96,7 @@ impl<'a> Executor<'a> {
|
||||||
if exec.named.contains_key("interactive") {
|
if exec.named.contains_key("interactive") {
|
||||||
self.cx.manager.active_mut().cd_interactive(url)
|
self.cx.manager.active_mut().cd_interactive(url)
|
||||||
} else {
|
} else {
|
||||||
self.cx.manager.active_mut().cd(url)
|
self.cx.manager.active_mut().cd(expand_url(url))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,12 +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"
|
||||||
bitflags = "^2"
|
bitflags = "^2"
|
||||||
crossterm = "^0"
|
crossterm = "^0"
|
||||||
futures = "^0"
|
futures = "^0"
|
||||||
libc = "^0"
|
libc = "^0"
|
||||||
parking_lot = "^0"
|
parking_lot = "^0"
|
||||||
ratatui = "^0"
|
percent-encoding = "^2"
|
||||||
regex = "^1"
|
ratatui = "^0"
|
||||||
tokio = { version = "^1", features = [ "parking_lot", "macros", "rt-multi-thread", "sync", "time", "fs" ] }
|
regex = "^1"
|
||||||
|
tokio = { version = "^1", features = [ "parking_lot", "macros", "rt-multi-thread", "sync", "time", "fs" ] }
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ 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,7 +13,7 @@ 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())
|
||||||
|
|
@ -37,9 +37,12 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
use std::{ffi::{OsStr, OsString}, fmt::{Debug, Formatter}, ops::{Deref, DerefMut}, path::{Path, PathBuf, MAIN_SEPARATOR}};
|
use std::{ffi::{OsStr, OsString}, fmt::{Debug, Formatter}, ops::{Deref, DerefMut}, path::{Path, PathBuf, MAIN_SEPARATOR}};
|
||||||
|
|
||||||
|
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 {
|
||||||
|
|
@ -122,6 +178,17 @@ impl Url {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn pop_dir(&mut self) -> bool {
|
||||||
|
if !self.was_dir() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if let Some(n) = self.path.file_name() {
|
||||||
|
self.path.set_file_name(n.to_owned());
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn into_dir(mut self) -> Self {
|
pub fn into_dir(mut self) -> Self {
|
||||||
if self.was_dir() {
|
if self.was_dir() {
|
||||||
|
|
@ -180,3 +247,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