mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
feat: new target parameter for create action
This commit is contained in:
parent
39527f488f
commit
e1cf588c2a
9 changed files with 90 additions and 23 deletions
|
|
@ -66,14 +66,8 @@ impl Actor for Trigger {
|
||||||
|
|
||||||
impl Trigger {
|
impl Trigger {
|
||||||
fn split_url(s: &str) -> Option<(UrlBuf, PathBufDyn)> {
|
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()?;
|
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/`
|
return None; // We don't complete a `sftp://test`, but `sftp://test/`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -84,7 +78,7 @@ impl Trigger {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Child
|
// 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
|
// Parent
|
||||||
let url = UrlCow::try_from((scheme.clone().zeroed(), path)).ok()?;
|
let url = UrlCow::try_from((scheme.clone().zeroed(), path)).ok()?;
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,16 @@
|
||||||
|
use std::pin::Pin;
|
||||||
|
|
||||||
use anyhow::{Result, bail};
|
use anyhow::{Result, bail};
|
||||||
|
use futures::{Stream, StreamExt};
|
||||||
|
use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||||
use yazi_config::{YAZI, popup::ConfirmCfg};
|
use yazi_config::{YAZI, popup::ConfirmCfg};
|
||||||
use yazi_fs::{FilesOp, file::File};
|
use yazi_fs::{FilesOp, file::File};
|
||||||
use yazi_macro::{input, ok_or_not_found, succ};
|
use yazi_macro::{input, ok_or_not_found, succ};
|
||||||
use yazi_parser::mgr::CreateForm;
|
use yazi_parser::mgr::CreateForm;
|
||||||
use yazi_proxy::{ConfirmProxy, MgrProxy};
|
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_vfs::{VfsFile, maybe_exists, provider};
|
||||||
use yazi_watcher::WATCHER;
|
use yazi_watcher::WATCHER;
|
||||||
use yazi_widgets::input::InputEvent;
|
|
||||||
|
|
||||||
use crate::{Actor, Ctx};
|
use crate::{Actor, Ctx};
|
||||||
|
|
||||||
|
|
@ -18,28 +21,35 @@ impl Actor for Create {
|
||||||
|
|
||||||
const NAME: &str = "create";
|
const NAME: &str = "create";
|
||||||
|
|
||||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
fn act(cx: &mut Ctx, CreateForm { target, dir, force }: Self::Form) -> Result<Data> {
|
||||||
let cwd = cx.cwd().to_owned();
|
let cwd = cx.cwd().to_owned();
|
||||||
let mut input = input!(cx, YAZI.input.create(form.dir))?;
|
|
||||||
|
let mut target: Pin<Box<dyn Stream<Item = StrandBuf> + 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 {
|
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() {
|
if name.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let Ok(new) = cwd.try_join(&name) else {
|
let Ok(new) = cwd.try_join(&name) else { return };
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
if !form.force
|
if !force
|
||||||
&& maybe_exists(&new).await
|
&& maybe_exists(&new).await
|
||||||
&& !ConfirmProxy::show(ConfirmCfg::overwrite(&new)).await
|
&& !ConfirmProxy::show(ConfirmCfg::overwrite(&new)).await
|
||||||
{
|
{
|
||||||
return;
|
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!();
|
succ!();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,15 @@
|
||||||
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
|
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use yazi_shared::event::ActionCow;
|
use yazi_shared::{event::ActionCow, strand::StrandBuf};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct CreateForm {
|
pub struct CreateForm {
|
||||||
|
#[serde(alias = "0")]
|
||||||
|
pub target: StrandBuf,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub dir: bool,
|
pub dir: bool,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub force: bool,
|
pub force: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<ActionCow> for CreateForm {
|
impl TryFrom<ActionCow> for CreateForm {
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,11 @@ pub trait Utf8BytePredictor {
|
||||||
pub struct AnyAsciiChar<'a>(&'a [u8]);
|
pub struct AnyAsciiChar<'a>(&'a [u8]);
|
||||||
|
|
||||||
impl<'a> AnyAsciiChar<'a> {
|
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<Self> {
|
pub fn new(chars: &'a [u8]) -> Option<Self> {
|
||||||
if chars.iter().all(|&b| b <= 0x7f) { Some(Self(chars)) } else { None }
|
if chars.iter().all(|&b| b <= 0x7f) { Some(Self(chars)) } else { None }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
40
yazi-shared/src/strand/de.rs
Normal file
40
yazi-shared/src/strand/de.rs
Normal file
|
|
@ -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<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
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<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
|
||||||
|
Ok(StrandBuf::Utf8(v.to_owned()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_string<E: de::Error>(self, v: String) -> Result<Self::Value, E> {
|
||||||
|
Ok(StrandBuf::Utf8(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_bytes<E: de::Error>(self, v: &[u8]) -> Result<Self::Value, E> {
|
||||||
|
Ok(StrandBuf::Bytes(v.to_owned()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_byte_buf<E: de::Error>(self, v: Vec<u8>) -> Result<Self::Value, E> {
|
||||||
|
Ok(StrandBuf::Bytes(v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deserializer.deserialize_any(V)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,6 +17,8 @@ pub trait StrandLike: AsStrand {
|
||||||
|
|
||||||
fn encoded_bytes(&self) -> &[u8] { self.as_strand().encoded_bytes() }
|
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 {
|
fn eq_ignore_ascii_case(&self, other: impl AsStrand) -> bool {
|
||||||
self.as_strand().eq_ignore_ascii_case(other)
|
self.as_strand().eq_ignore_ascii_case(other)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
pub fn eq_ignore_ascii_case(self, other: impl AsStrand) -> bool {
|
||||||
self.encoded_bytes().eq_ignore_ascii_case(other.as_strand().encoded_bytes())
|
self.encoded_bytes().eq_ignore_ascii_case(other.as_strand().encoded_bytes())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,21 @@ pub enum InputEvent {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InputEvent {
|
impl InputEvent {
|
||||||
|
pub fn is_submit(&self) -> bool { matches!(self, Self::Submit(_)) }
|
||||||
|
|
||||||
pub fn value(&self) -> &str {
|
pub fn value(&self) -> &str {
|
||||||
match self {
|
match self {
|
||||||
Self::Submit(v) | Self::Cancel(v) | Self::Type(v) | Self::Trigger(v, _) => v.as_str(),
|
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<T, F>(self, f: F) -> Option<T>
|
||||||
|
where
|
||||||
|
F: FnOnce(String) -> T,
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Self::Submit(v) => Some(f(v)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue