refactor: new yazi-widgets crate

This commit is contained in:
sxyazi 2025-03-19 20:44:12 +08:00
parent 7632163678
commit 3fb8c34185
No known key found for this signature in database
40 changed files with 255 additions and 181 deletions

17
Cargo.lock generated
View file

@ -3433,6 +3433,7 @@ dependencies = [
"yazi-proxy", "yazi-proxy",
"yazi-scheduler", "yazi-scheduler",
"yazi-shared", "yazi-shared",
"yazi-widgets",
] ]
[[package]] [[package]]
@ -3501,6 +3502,7 @@ dependencies = [
"yazi-plugin", "yazi-plugin",
"yazi-proxy", "yazi-proxy",
"yazi-shared", "yazi-shared",
"yazi-widgets",
] ]
[[package]] [[package]]
@ -3630,6 +3632,21 @@ dependencies = [
"yazi-macro", "yazi-macro",
] ]
[[package]]
name = "yazi-widgets"
version = "25.3.7"
dependencies = [
"futures",
"tokio",
"unicode-width 0.2.0",
"yazi-codegen",
"yazi-config",
"yazi-macro",
"yazi-plugin",
"yazi-proxy",
"yazi-shared",
]
[[package]] [[package]]
name = "yoke" name = "yoke"
version = "0.7.5" version = "0.7.5"

View file

@ -20,6 +20,7 @@ yazi-plugin = { path = "../yazi-plugin", version = "25.3.7" }
yazi-proxy = { path = "../yazi-proxy", version = "25.3.7" } yazi-proxy = { path = "../yazi-proxy", version = "25.3.7" }
yazi-scheduler = { path = "../yazi-scheduler", version = "25.3.7" } yazi-scheduler = { path = "../yazi-scheduler", version = "25.3.7" }
yazi-shared = { path = "../yazi-shared", version = "25.3.7" } yazi-shared = { path = "../yazi-shared", version = "25.3.7" }
yazi-widgets = { path = "../yazi-widgets", version = "25.3.7" }
# External dependencies # External dependencies
anyhow = { workspace = true } anyhow = { workspace = true }

View file

@ -1,3 +1,5 @@
use std::mem;
use yazi_macro::render; use yazi_macro::render;
use yazi_proxy::InputProxy; use yazi_proxy::InputProxy;
use yazi_shared::event::CmdCow; use yazi_shared::event::CmdCow;
@ -23,7 +25,6 @@ impl Cmp {
} }
self.caches.clear(); self.caches.clear();
self.visible = false; render!(mem::replace(&mut self.visible, false));
render!();
} }
} }

View file

@ -18,17 +18,15 @@ impl From<bool> for Opt {
impl Input { impl Input {
#[yazi_codegen::command] #[yazi_codegen::command]
pub fn close(&mut self, opt: Opt) { pub fn close(&mut self, opt: Opt) {
if self.completion { self.visible = false;
CmpProxy::close(); self.ticket.next();
}
if let Some(cb) = self.callback.take() { if let Some(cb) = self.tx.take() {
let value = self.snap_mut().value.clone(); let value = self.snap().value.clone();
_ = cb.send(if opt.submit { Ok(value) } else { Err(InputError::Canceled(value)) }); _ = cb.send(if opt.submit { Ok(value) } else { Err(InputError::Canceled(value)) });
} }
self.ticket = self.ticket.wrapping_add(1); CmpProxy::close();
self.visible = false;
render!(); render!();
} }
} }

View file

@ -1,8 +1,9 @@
use yazi_macro::render; use yazi_macro::render;
use yazi_proxy::CmpProxy; use yazi_proxy::CmpProxy;
use yazi_shared::event::CmdCow; use yazi_shared::event::CmdCow;
use yazi_widgets::input::InputOp;
use crate::input::{Input, InputMode, op::InputOp}; use crate::input::Input;
struct Opt; struct Opt;
@ -16,28 +17,16 @@ impl From<()> for Opt {
impl Input { impl Input {
#[yazi_codegen::command] #[yazi_codegen::command]
pub fn escape(&mut self, _: Opt) { pub fn escape(&mut self, _: Opt) {
let snap = self.snap_mut(); use yazi_widgets::input::InputMode as M;
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 { let mode = self.snap().mode;
CmpProxy::close(); match mode {
} M::Normal if self.snap_mut().op == InputOp::None => self.close(false),
} M::Insert => CmpProxy::close(),
InputMode::Replace => { M::Normal | M::Replace => {}
snap.mode = InputMode::Normal;
}
} }
self.snaps.tag(self.limit()); self.inner.escape(());
render!(); render!();
} }
} }

View file

@ -1 +1 @@
yazi_macro::mod_flat!(backspace backward close complete delete escape forward insert kill move_ paste redo replace show type_ undo visual yank); yazi_macro::mod_flat!(close escape show);

View file

@ -20,7 +20,7 @@ impl TryFrom<CmdCow> for Opt {
impl Input { impl Input {
pub fn show(&mut self, opt: impl TryInto<Opt>) { pub fn show(&mut self, opt: impl TryInto<Opt>) {
let Ok(opt) = opt.try_into() else { return }; let Ok(opt): Result<Opt, _> = opt.try_into() else { return };
self.close(false); self.close(false);
self.visible = true; self.visible = true;
@ -28,17 +28,26 @@ impl Input {
self.position = opt.cfg.position; self.position = opt.cfg.position;
// Typing // Typing
self.callback = Some(opt.tx); self.tx = Some(opt.tx.clone());
self.realtime = opt.cfg.realtime;
self.completion = opt.cfg.completion;
// Shell // Shell
self.highlight = opt.cfg.highlight; self.highlight = opt.cfg.highlight;
// Reset snaps // Reset input
self.snaps.reset(opt.cfg.value, self.limit()); let ticket = self.ticket.clone();
let cb: Box<dyn Fn(&str, &str)> = Box::new(move |before, after| {
if opt.cfg.realtime {
opt.tx.send(Err(InputError::Typed(format!("{before}{after}")))).ok();
}
if opt.cfg.completion {
opt.tx.send(Err(InputError::Completed(before.to_owned(), ticket.current()))).ok();
}
});
self.inner = yazi_widgets::input::Input::new(opt.cfg.value, self.limit, cb);
// Set cursor after reset // Set cursor after reset
// TODO: remove this
if let Some(cursor) = opt.cfg.cursor { if let Some(cursor) = opt.cfg.cursor {
self.snap_mut().cursor = cursor; self.snap_mut().cursor = cursor;
self.move_(0); self.move_(0);

View file

@ -1,131 +1,31 @@
use std::ops::Range; use std::{ops::{Deref, DerefMut}, rc::Rc};
use tokio::sync::mpsc::UnboundedSender; use tokio::sync::mpsc::UnboundedSender;
use unicode_width::UnicodeWidthStr; use yazi_config::popup::Position;
use yazi_config::{INPUT, popup::Position}; use yazi_shared::{Ids, errors::InputError};
use yazi_plugin::CLIPBOARD;
use yazi_shared::errors::InputError;
use super::{InputSnap, InputSnaps, mode::InputMode, op::InputOp};
#[derive(Default)] #[derive(Default)]
pub struct Input { pub struct Input {
pub(super) snaps: InputSnaps, pub(super) inner: yazi_widgets::input::Input,
pub ticket: usize,
pub visible: bool,
pub visible: bool,
pub title: String, pub title: String,
pub position: Position, pub position: Position,
// Typing // Typing
pub(super) callback: Option<UnboundedSender<Result<String, InputError>>>, pub(super) tx: Option<UnboundedSender<Result<String, InputError>>>,
pub(super) realtime: bool, pub(super) ticket: Rc<Ids>,
pub(super) completion: bool,
// Shell // Shell
pub highlight: bool, pub highlight: bool,
} }
impl Input { impl Deref for Input {
#[inline] type Target = yazi_widgets::input::Input;
pub(super) fn limit(&self) -> usize {
self.position.offset.width.saturating_sub(INPUT.border()) as usize
}
pub(super) fn handle_op(&mut self, cursor: usize, include: bool) -> bool { fn deref(&self) -> &Self::Target { &self.inner }
let old = self.snap().clone();
let snap = self.snap_mut();
match snap.op {
InputOp::None | InputOp::Select(_) => {
snap.cursor = cursor;
}
InputOp::Delete(cut, insert, _) => {
let range = snap.op.range(cursor, include).unwrap();
let Range { start, end } = snap.idx(range.start)..snap.idx(range.end);
let drain = snap.value.drain(start.unwrap()..end.unwrap()).collect::<String>();
if cut {
futures::executor::block_on(CLIPBOARD.set(&drain));
}
snap.op = InputOp::None;
snap.mode = if insert { InputMode::Insert } else { InputMode::Normal };
snap.cursor = range.start;
}
InputOp::Yank(_) => {
let range = snap.op.range(cursor, include).unwrap();
let Range { start, end } = snap.idx(range.start)..snap.idx(range.end);
let yanked = &snap.value[start.unwrap()..end.unwrap()];
snap.op = InputOp::None;
futures::executor::block_on(CLIPBOARD.set(yanked));
}
};
snap.cursor = snap.count().saturating_sub(snap.mode.delta()).min(snap.cursor);
if snap == &old {
return false;
}
if !matches!(old.op, InputOp::None | InputOp::Select(_)) {
self.snaps.tag(self.limit()).then(|| self.flush_value());
}
true
}
pub(super) fn flush_value(&mut self) {
let Some(tx) = &self.callback else { return };
self.ticket = self.ticket.wrapping_add(1);
if self.realtime {
let value = self.snap().value.clone();
tx.send(Err(InputError::Typed(value))).ok();
}
if self.completion {
let before = self.partition()[0].to_owned();
tx.send(Err(InputError::Completed(before, self.ticket))).ok();
}
}
} }
impl Input { impl DerefMut for Input {
#[inline] fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner }
pub fn value(&self) -> &str { self.snap().slice(self.snap().window(self.limit())) }
#[inline]
pub fn mode(&self) -> InputMode { self.snap().mode }
#[inline]
pub fn cursor(&self) -> u16 {
let snap = self.snap();
snap.slice(snap.offset..snap.cursor).width() as u16
}
pub fn selected(&self) -> Option<Range<u16>> {
let snap = self.snap();
let start = snap.op.start()?;
let (start, end) =
if start < snap.cursor { (start, snap.cursor) } else { (snap.cursor + 1, start + 1) };
let win = snap.window(self.limit());
let Range { start, end } = start.max(win.start)..end.min(win.end);
let s = snap.slice(snap.offset..start).width() as u16;
Some(s..s + snap.slice(start..end).width() as u16)
}
#[inline]
pub fn partition(&self) -> [&str; 2] {
let snap = self.snap();
let idx = snap.idx(snap.cursor).unwrap();
[&snap.value[..idx], &snap.value[idx..]]
}
#[inline]
pub(super) fn snap(&self) -> &InputSnap { self.snaps.current() }
#[inline]
pub(super) fn snap_mut(&mut self) -> &mut InputSnap { self.snaps.current_mut() }
} }

View file

@ -1,3 +1,3 @@
yazi_macro::mod_pub!(commands); yazi_macro::mod_pub!(commands);
yazi_macro::mod_flat!(input mode op snap snaps); yazi_macro::mod_flat!(input);

View file

@ -24,6 +24,7 @@ yazi-macro = { path = "../yazi-macro", version = "25.3.7" }
yazi-plugin = { path = "../yazi-plugin", version = "25.3.7" } yazi-plugin = { path = "../yazi-plugin", version = "25.3.7" }
yazi-proxy = { path = "../yazi-proxy", version = "25.3.7" } yazi-proxy = { path = "../yazi-proxy", version = "25.3.7" }
yazi-shared = { path = "../yazi-shared", version = "25.3.7" } yazi-shared = { path = "../yazi-shared", version = "25.3.7" }
yazi-widgets = { path = "../yazi-widgets", version = "25.3.7" }
# External dependencies # External dependencies
anyhow = { workspace = true } anyhow = { workspace = true }

View file

@ -3,9 +3,9 @@ use std::sync::atomic::Ordering;
use anyhow::Result; use anyhow::Result;
use crossterm::event::KeyEvent; use crossterm::event::KeyEvent;
use yazi_config::keymap::Key; use yazi_config::keymap::Key;
use yazi_core::input::InputMode;
use yazi_macro::emit; use yazi_macro::emit;
use yazi_shared::event::{CmdCow, Event, NEED_RENDER}; use yazi_shared::event::{CmdCow, Event, NEED_RENDER};
use yazi_widgets::input::InputMode;
use crate::{Ctx, Executor, Router, Signals, Term, lives::Lives}; use crate::{Ctx, Executor, Router, Signals, Term, lives::Lives};

View file

@ -1,5 +1,5 @@
use yazi_core::input::InputMode;
use yazi_shared::{Layer, event::CmdCow}; use yazi_shared::{Layer, event::CmdCow};
use yazi_widgets::input::InputMode;
use crate::app::App; use crate::app::App;

View file

@ -4,8 +4,8 @@ use anyhow::{Result, bail};
use ratatui::{buffer::Buffer, layout::Rect, text::Line, widgets::{Block, BorderType, Paragraph, Widget}}; use ratatui::{buffer::Buffer, layout::Rect, text::Line, widgets::{Block, BorderType, Paragraph, Widget}};
use syntect::easy::HighlightLines; use syntect::easy::HighlightLines;
use yazi_config::{PREVIEW, THEME}; use yazi_config::{PREVIEW, THEME};
use yazi_core::input::InputMode;
use yazi_plugin::external::Highlighter; use yazi_plugin::external::Highlighter;
use yazi_widgets::input::InputMode;
use crate::{Ctx, Term}; use crate::{Ctx, Term};

View file

@ -1,5 +1,5 @@
use yazi_macro::emit; use yazi_macro::emit;
use yazi_shared::event::Cmd; use yazi_shared::{Id, event::Cmd};
pub struct CmpProxy; pub struct CmpProxy;
@ -10,7 +10,7 @@ impl CmpProxy {
} }
#[inline] #[inline]
pub fn trigger(word: &str, ticket: usize) { pub fn trigger(word: &str, ticket: Id) {
emit!(Call(Cmd::args("cmp:trigger", &[word]).with("ticket", ticket))); emit!(Call(Cmd::args("cmp:trigger", &[word]).with("ticket", ticket)));
} }
} }

View file

@ -1,9 +1,11 @@
use std::{error::Error, fmt::{self, Display}}; use std::{error::Error, fmt::{self, Display}};
use crate::Id;
#[derive(Debug)] #[derive(Debug)]
pub enum InputError { pub enum InputError {
Typed(String), Typed(String),
Completed(String, usize), Completed(String, Id),
Canceled(String), Canceled(String),
} }

23
yazi-widgets/Cargo.toml Normal file
View file

@ -0,0 +1,23 @@
[package]
name = "yazi-widgets"
version = "25.3.7"
edition = "2021"
license = "MIT"
authors = [ "sxyazi <sxyazi@gmail.com>" ]
description = "Yazi user interface widgets"
homepage = "https://yazi-rs.github.io"
repository = "https://github.com/sxyazi/yazi"
rust-version = "1.83.0"
[dependencies]
yazi-codegen = { path = "../yazi-codegen", version = "25.3.7" }
yazi-config = { path = "../yazi-config", version = "25.3.7" }
yazi-macro = { path = "../yazi-macro", version = "25.3.7" }
yazi-plugin = { path = "../yazi-plugin", version = "25.3.7" }
yazi-proxy = { path = "../yazi-proxy", version = "25.3.7" }
yazi-shared = { path = "../yazi-shared", version = "25.3.7" }
# External dependencies
futures = { workspace = true }
tokio = { workspace = true }
unicode-width = { workspace = true }

View file

@ -12,15 +12,15 @@ const SEPARATOR: [char; 2] = ['/', '\\'];
const SEPARATOR: char = std::path::MAIN_SEPARATOR; const SEPARATOR: char = std::path::MAIN_SEPARATOR;
struct Opt { struct Opt {
word: Cow<'static, str>, word: Cow<'static, str>,
ticket: usize, _ticket: usize, // FIXME
} }
impl From<CmdCow> for Opt { impl From<CmdCow> for Opt {
fn from(mut c: CmdCow) -> Self { fn from(mut c: CmdCow) -> Self {
Self { Self {
word: c.take_first_str().unwrap_or_default(), word: c.take_first_str().unwrap_or_default(),
ticket: c.get("ticket").and_then(Data::as_usize).unwrap_or(0), _ticket: c.get("ticket").and_then(Data::as_usize).unwrap_or(0),
} }
} }
} }
@ -28,11 +28,7 @@ impl From<CmdCow> for Opt {
impl Input { impl Input {
#[yazi_codegen::command] #[yazi_codegen::command]
pub fn complete(&mut self, opt: Opt) { pub fn complete(&mut self, opt: Opt) {
if self.ticket != opt.ticket { let (before, after) = self.partition();
return;
}
let [before, after] = self.partition();
let new = if let Some((prefix, _)) = before.rsplit_once(SEPARATOR) { let new = if let Some((prefix, _)) = before.rsplit_once(SEPARATOR) {
format!("{prefix}/{}{after}", opt.word).replace(SEPARATOR, MAIN_SEPARATOR_STR) format!("{prefix}/{}{after}", opt.word).replace(SEPARATOR, MAIN_SEPARATOR_STR)
} else { } else {

View file

@ -0,0 +1,27 @@
use crate::input::{Input, InputMode, op::InputOp};
struct Opt;
impl From<()> for Opt {
fn from(_: ()) -> Self { Self }
}
impl Input {
#[yazi_codegen::command]
pub fn escape(&mut self, _: Opt) {
let snap = self.snap_mut();
match snap.mode {
InputMode::Normal => {
snap.op = InputOp::None;
}
InputMode::Insert => {
snap.mode = InputMode::Normal;
self.move_(-1);
}
InputMode::Replace => {
snap.mode = InputMode::Normal;
}
}
self.snaps.tag(self.limit);
}
}

View file

@ -0,0 +1 @@
yazi_macro::mod_flat!(backspace backward complete delete escape forward insert kill move_ paste redo replace type_ undo visual yank);

View file

@ -33,7 +33,7 @@ impl Input {
render!(self.handle_op(opt.step.cursor(snap), false)); render!(self.handle_op(opt.step.cursor(snap), false));
let (limit, snap) = (self.limit(), self.snap_mut()); let (limit, snap) = (self.limit, self.snap_mut());
if snap.offset > snap.cursor { if snap.offset > snap.cursor {
snap.offset = snap.cursor; snap.offset = snap.cursor;
} else if snap.value.is_empty() { } else if snap.value.is_empty() {

View file

@ -27,6 +27,6 @@ impl Input {
} }
render!(); render!();
self.snaps.tag(self.limit()).then(|| self.flush_value()); self.snaps.tag(self.limit).then(|| self.flush_value());
} }
} }

View file

@ -0,0 +1,108 @@
use std::ops::Range;
use unicode_width::UnicodeWidthStr;
use yazi_plugin::CLIPBOARD;
use super::{InputSnap, InputSnaps, mode::InputMode, op::InputOp};
#[derive(Default)]
pub struct Input {
pub snaps: InputSnaps,
pub limit: usize,
pub callback: Option<Box<dyn Fn(&str, &str)>>,
}
impl Input {
pub fn new(value: String, limit: usize, callback: Box<dyn Fn(&str, &str)>) -> Self {
Self { snaps: InputSnaps::new(value, limit), limit, callback: Some(callback) }
}
pub(super) fn handle_op(&mut self, cursor: usize, include: bool) -> bool {
let old = self.snap().clone();
let snap = self.snap_mut();
match snap.op {
InputOp::None | InputOp::Select(_) => {
snap.cursor = cursor;
}
InputOp::Delete(cut, insert, _) => {
let range = snap.op.range(cursor, include).unwrap();
let Range { start, end } = snap.idx(range.start)..snap.idx(range.end);
let drain = snap.value.drain(start.unwrap()..end.unwrap()).collect::<String>();
if cut {
futures::executor::block_on(CLIPBOARD.set(&drain));
}
snap.op = InputOp::None;
snap.mode = if insert { InputMode::Insert } else { InputMode::Normal };
snap.cursor = range.start;
}
InputOp::Yank(_) => {
let range = snap.op.range(cursor, include).unwrap();
let Range { start, end } = snap.idx(range.start)..snap.idx(range.end);
let yanked = &snap.value[start.unwrap()..end.unwrap()];
snap.op = InputOp::None;
futures::executor::block_on(CLIPBOARD.set(yanked));
}
};
snap.cursor = snap.count().saturating_sub(snap.mode.delta()).min(snap.cursor);
if snap == &old {
return false;
}
if !matches!(old.op, InputOp::None | InputOp::Select(_)) {
self.snaps.tag(self.limit).then(|| self.flush_value());
}
true
}
pub(super) fn flush_value(&mut self) {
if let Some(cb) = &self.callback {
let (before, after) = self.partition();
cb(before, after);
}
}
}
impl Input {
#[inline]
pub fn value(&self) -> &str { self.snap().slice(self.snap().window(self.limit)) }
#[inline]
pub fn mode(&self) -> InputMode { self.snap().mode }
#[inline]
pub fn cursor(&self) -> u16 {
let snap = self.snap();
snap.slice(snap.offset..snap.cursor).width() as u16
}
pub fn selected(&self) -> Option<Range<u16>> {
let snap = self.snap();
let start = snap.op.start()?;
let (start, end) =
if start < snap.cursor { (start, snap.cursor) } else { (snap.cursor + 1, start + 1) };
let win = snap.window(self.limit);
let Range { start, end } = start.max(win.start)..end.min(win.end);
let s = snap.slice(snap.offset..start).width() as u16;
Some(s..s + snap.slice(start..end).width() as u16)
}
#[inline]
pub fn partition(&self) -> (&str, &str) {
let snap = self.snap();
let idx = snap.idx(snap.cursor).unwrap();
(&snap.value[..idx], &snap.value[idx..])
}
#[inline]
pub fn snap(&self) -> &InputSnap { self.snaps.current() }
#[inline]
pub fn snap_mut(&mut self) -> &mut InputSnap { self.snaps.current_mut() }
}

View file

@ -0,0 +1,3 @@
yazi_macro::mod_pub!(commands);
yazi_macro::mod_flat!(input mode op snap snaps);

View file

@ -5,14 +5,14 @@ use unicode_width::UnicodeWidthChar;
use super::{InputMode, InputOp}; use super::{InputMode, InputOp};
#[derive(Clone, Debug, Default, PartialEq, Eq)] #[derive(Clone, Debug, Default, PartialEq, Eq)]
pub(super) struct InputSnap { pub struct InputSnap {
pub(super) value: String, pub value: String,
pub(super) op: InputOp, pub op: InputOp,
pub(super) mode: InputMode, pub mode: InputMode,
pub(super) offset: usize, pub offset: usize,
pub(super) cursor: usize, pub cursor: usize,
} }
impl InputSnap { impl InputSnap {
@ -22,7 +22,7 @@ impl InputSnap {
op: Default::default(), op: Default::default(),
mode: Default::default(), mode: Default::default(),
offset: usize::MAX, offset: usize::MAX,
cursor: usize::MAX, cursor: usize::MAX,
}; };

View file

@ -3,19 +3,16 @@ use std::mem;
use super::InputSnap; use super::InputSnap;
#[derive(Default, PartialEq, Eq)] #[derive(Default, PartialEq, Eq)]
pub(super) struct InputSnaps { pub struct InputSnaps {
idx: usize, idx: usize,
versions: Vec<InputSnap>, versions: Vec<InputSnap>,
current: InputSnap, current: InputSnap,
} }
impl InputSnaps { impl InputSnaps {
#[inline] pub fn new(value: String, limit: usize) -> Self {
pub(super) fn reset(&mut self, value: String, limit: usize) { let current = InputSnap::new(value, limit);
self.idx = 0; Self { idx: 0, versions: vec![current.clone()], current }
self.versions.clear();
self.versions.push(InputSnap::new(value, limit));
self.current = self.versions[0].clone();
} }
pub(super) fn tag(&mut self, limit: usize) -> bool { pub(super) fn tag(&mut self, limit: usize) -> bool {
@ -65,7 +62,7 @@ impl InputSnaps {
impl InputSnaps { impl InputSnaps {
#[inline] #[inline]
pub(super) fn current(&self) -> &InputSnap { &self.current } pub fn current(&self) -> &InputSnap { &self.current }
#[inline] #[inline]
pub(super) fn current_mut(&mut self) -> &mut InputSnap { &mut self.current } pub(super) fn current_mut(&mut self) -> &mut InputSnap { &mut self.current }

1
yazi-widgets/src/lib.rs Normal file
View file

@ -0,0 +1 @@
yazi_macro::mod_pub!(input);