diff --git a/yazi-actor/src/cmp/trigger.rs b/yazi-actor/src/cmp/trigger.rs index 964d715e..bb4a631e 100644 --- a/yazi-actor/src/cmp/trigger.rs +++ b/yazi-actor/src/cmp/trigger.rs @@ -66,14 +66,8 @@ impl Actor for Trigger { impl Trigger { fn split_url(s: &str) -> Option<(UrlBuf, PathBufDyn)> { - let sep = if cfg!(windows) { - AnyAsciiChar::new(b"/\\").unwrap() - } else { - AnyAsciiChar::new(b"/").unwrap() - }; - let (scheme, path) = SchemeCow::parse(s.as_bytes()).ok()?; - if path.is_empty() && !sep.predicate(s.bytes().last()?) { + if path.is_empty() && !AnyAsciiChar::SEP.predicate(s.bytes().last()?) { return None; // We don't complete a `sftp://test`, but `sftp://test/` } @@ -84,7 +78,7 @@ impl Trigger { } // Child - let child = path.rsplit_pred(sep).map_or(path.as_path(), |(_, c)| c).to_owned(); + let child = path.rsplit_pred(AnyAsciiChar::SEP).map_or(path.as_path(), |(_, c)| c).to_owned(); // Parent let url = UrlCow::try_from((scheme.clone().zeroed(), path)).ok()?; diff --git a/yazi-actor/src/mgr/create.rs b/yazi-actor/src/mgr/create.rs index 08c6b027..e4ae292e 100644 --- a/yazi-actor/src/mgr/create.rs +++ b/yazi-actor/src/mgr/create.rs @@ -1,13 +1,16 @@ +use std::pin::Pin; + use anyhow::{Result, bail}; +use futures::{Stream, StreamExt}; +use tokio_stream::wrappers::UnboundedReceiverStream; use yazi_config::{YAZI, popup::ConfirmCfg}; use yazi_fs::{FilesOp, file::File}; use yazi_macro::{input, ok_or_not_found, succ}; use yazi_parser::mgr::CreateForm; use yazi_proxy::{ConfirmProxy, MgrProxy}; -use yazi_shared::{data::Data, url::{UrlBuf, UrlLike}}; +use yazi_shared::{AnyAsciiChar, BytePredictor, data::Data, strand::{StrandBuf, StrandLike}, url::{UrlBuf, UrlLike}}; use yazi_vfs::{VfsFile, maybe_exists, provider}; use yazi_watcher::WATCHER; -use yazi_widgets::input::InputEvent; use crate::{Actor, Ctx}; @@ -18,28 +21,35 @@ impl Actor for Create { const NAME: &str = "create"; - fn act(cx: &mut Ctx, form: Self::Form) -> Result { + fn act(cx: &mut Ctx, CreateForm { target, dir, force }: Self::Form) -> Result { let cwd = cx.cwd().to_owned(); - let mut input = input!(cx, YAZI.input.create(form.dir))?; + + let mut target: Pin + Send>> = if target.is_empty() { + let input = input!(cx, YAZI.input.create(dir))?; + Box::pin( + UnboundedReceiverStream::new(input).filter_map(|event| async { event.map(Into::into) }), + ) + } else { + Box::pin(tokio_stream::iter(vec![target])) + }; tokio::spawn(async move { - let Some(InputEvent::Submit(name)) = input.recv().await else { return }; + let Some(name) = target.next().await else { return }; if name.is_empty() { return; } - let Ok(new) = cwd.try_join(&name) else { - return; - }; + let Ok(new) = cwd.try_join(&name) else { return }; - if !form.force + if !force && maybe_exists(&new).await && !ConfirmProxy::show(ConfirmCfg::overwrite(&new)).await { return; } - _ = Self::r#do(new, form.dir || name.ends_with('/') || name.ends_with('\\')).await; + let end_sep = AnyAsciiChar::SEP.predicate(*name.encoded_bytes().last().unwrap()); + _ = Self::r#do(new, dir || end_sep).await; }); succ!(); } diff --git a/yazi-parser/src/mgr/create.rs b/yazi-parser/src/mgr/create.rs index bf8dc1ab..fc8c1cf8 100644 --- a/yazi-parser/src/mgr/create.rs +++ b/yazi-parser/src/mgr/create.rs @@ -1,13 +1,15 @@ use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; use serde::Deserialize; -use yazi_shared::event::ActionCow; +use yazi_shared::{event::ActionCow, strand::StrandBuf}; #[derive(Debug, Deserialize)] pub struct CreateForm { + #[serde(alias = "0", default)] + pub target: StrandBuf, #[serde(default)] - pub dir: bool, + pub dir: bool, #[serde(default)] - pub force: bool, + pub force: bool, } impl TryFrom for CreateForm { diff --git a/yazi-shared/src/predictor.rs b/yazi-shared/src/predictor.rs index 4603e2e9..6007c573 100644 --- a/yazi-shared/src/predictor.rs +++ b/yazi-shared/src/predictor.rs @@ -12,6 +12,11 @@ pub trait Utf8BytePredictor { pub struct AnyAsciiChar<'a>(&'a [u8]); impl<'a> AnyAsciiChar<'a> { + #[cfg(windows)] + pub const SEP: Self = Self(b"/\\"); + #[cfg(not(windows))] + pub const SEP: Self = Self(b"/"); + pub fn new(chars: &'a [u8]) -> Option { if chars.iter().all(|&b| b <= 0x7f) { Some(Self(chars)) } else { None } } diff --git a/yazi-shared/src/strand/de.rs b/yazi-shared/src/strand/de.rs new file mode 100644 index 00000000..0f6ccff8 --- /dev/null +++ b/yazi-shared/src/strand/de.rs @@ -0,0 +1,40 @@ +use std::fmt; + +use serde::{Deserialize, Deserializer, de::{self, Visitor}}; + +use crate::strand::StrandBuf; + +impl<'de> Deserialize<'de> for StrandBuf { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct V; + + impl<'de> Visitor<'de> for V { + type Value = StrandBuf; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("a string or bytes") + } + + fn visit_str(self, v: &str) -> Result { + Ok(StrandBuf::Utf8(v.to_owned())) + } + + fn visit_string(self, v: String) -> Result { + Ok(StrandBuf::Utf8(v)) + } + + fn visit_bytes(self, v: &[u8]) -> Result { + Ok(StrandBuf::Bytes(v.to_owned())) + } + + fn visit_byte_buf(self, v: Vec) -> Result { + Ok(StrandBuf::Bytes(v)) + } + } + + deserializer.deserialize_any(V) + } +} diff --git a/yazi-shared/src/strand/like.rs b/yazi-shared/src/strand/like.rs index 297fc32f..e6463dae 100644 --- a/yazi-shared/src/strand/like.rs +++ b/yazi-shared/src/strand/like.rs @@ -17,6 +17,8 @@ pub trait StrandLike: AsStrand { fn encoded_bytes(&self) -> &[u8] { self.as_strand().encoded_bytes() } + fn ends_with(&self, needle: impl AsStrand) -> bool { self.as_strand().ends_with(needle) } + fn eq_ignore_ascii_case(&self, other: impl AsStrand) -> bool { self.as_strand().eq_ignore_ascii_case(other) } diff --git a/yazi-shared/src/strand/mod.rs b/yazi-shared/src/strand/mod.rs index af6a08c4..ccbb363f 100644 --- a/yazi-shared/src/strand/mod.rs +++ b/yazi-shared/src/strand/mod.rs @@ -1 +1 @@ -yazi_macro::mod_flat!(buf conversion cow error extensions kind like strand view); +yazi_macro::mod_flat!(buf conversion cow de error extensions kind like strand view); diff --git a/yazi-shared/src/strand/strand.rs b/yazi-shared/src/strand/strand.rs index 4337bf29..b66d341f 100644 --- a/yazi-shared/src/strand/strand.rs +++ b/yazi-shared/src/strand/strand.rs @@ -156,6 +156,10 @@ impl<'a> Strand<'a> { } } + pub fn ends_with(self, needle: impl AsStrand) -> bool { + self.encoded_bytes().ends_with(needle.as_strand().encoded_bytes()) + } + pub fn eq_ignore_ascii_case(self, other: impl AsStrand) -> bool { self.encoded_bytes().eq_ignore_ascii_case(other.as_strand().encoded_bytes()) } diff --git a/yazi-widgets/src/input/event.rs b/yazi-widgets/src/input/event.rs index 7e632e7b..82ed4fa6 100644 --- a/yazi-widgets/src/input/event.rs +++ b/yazi-widgets/src/input/event.rs @@ -12,11 +12,21 @@ pub enum InputEvent { } impl InputEvent { + pub fn is_submit(&self) -> bool { matches!(self, Self::Submit(_)) } + pub fn value(&self) -> &str { match self { Self::Submit(v) | Self::Cancel(v) | Self::Type(v) | Self::Trigger(v, _) => v.as_str(), } } - pub fn is_submit(&self) -> bool { matches!(self, Self::Submit(_)) } + pub fn map(self, f: F) -> Option + where + F: FnOnce(String) -> T, + { + match self { + Self::Submit(v) => Some(f(v)), + _ => None, + } + } }