This commit is contained in:
sxyazi 2023-11-08 10:07:52 +08:00
parent 0932752234
commit 8100598224
No known key found for this signature in database
11 changed files with 117 additions and 35 deletions

1
Cargo.lock generated
View file

@ -2565,6 +2565,7 @@ dependencies = [
"futures", "futures",
"libc", "libc",
"parking_lot", "parking_lot",
"percent-encoding",
"ratatui", "ratatui",
"regex", "regex",
"tokio", "tokio",

View file

@ -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();
}; };

View file

@ -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)));
} }

View file

@ -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(

View file

@ -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

View file

@ -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)));

View file

@ -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();
} }

View file

@ -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))
} }
} }

View file

@ -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" ] }

View file

@ -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: &regex::Captures| { let s = re.replace_all(&s, |caps: &regex::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
} }

View file

@ -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,
}
}
}