fix: ignore NumLock state when comparing keybindings (#4071)

This commit is contained in:
三咲雅 misaki masa 2026-06-20 23:53:21 +08:00 committed by sxyazi
parent 0ba1811af3
commit 4f45ddb514
No known key found for this signature in database
40 changed files with 139 additions and 116 deletions

23
Cargo.lock generated
View file

@ -543,9 +543,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.64" version = "1.2.65"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dad887fd958be91b5098c0248def011f4523ab786cd411be668777e55063501f" checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96"
dependencies = [ dependencies = [
"find-msvc-tools", "find-msvc-tools",
"jobserver", "jobserver",
@ -2880,14 +2880,14 @@ checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
[[package]] [[package]]
name = "ratatui-core" name = "ratatui-core"
version = "0.1.1" version = "0.1.2"
source = "git+https://github.com/yazi-rs/ratatui.git?rev=a1c4922a242861d755163ae85d7e41b85ed277d2#a1c4922a242861d755163ae85d7e41b85ed277d2" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbb175c433c8e28a809d1f5773a2ae96e68c0ce40db865cbab1020bf33ae479c"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"compact_str", "compact_str",
"critical-section", "critical-section",
"hashbrown 0.17.1", "hashbrown 0.17.1",
"indoc",
"itertools", "itertools",
"kasuari", "kasuari",
"lru", "lru",
@ -2902,8 +2902,9 @@ dependencies = [
[[package]] [[package]]
name = "ratatui-widgets" name = "ratatui-widgets"
version = "0.3.1" version = "0.3.2"
source = "git+https://github.com/yazi-rs/ratatui.git?rev=a1c4922a242861d755163ae85d7e41b85ed277d2#a1c4922a242861d755163ae85d7e41b85ed277d2" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66e3d19bcc9130ca376277d93b60767ff121ace3be06f5f95f81dd68956407d1"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"hashbrown 0.17.1", "hashbrown 0.17.1",
@ -4705,10 +4706,8 @@ dependencies = [
"indexmap 2.14.0", "indexmap 2.14.0",
"libc", "libc",
"mlua", "mlua",
"parking_lot",
"paste", "paste",
"ratatui-core", "ratatui-core",
"ratatui-widgets",
"scopeguard", "scopeguard",
"tokio", "tokio",
"tokio-stream", "tokio-stream",
@ -4867,7 +4866,6 @@ dependencies = [
"regex", "regex",
"serde", "serde",
"serde_with", "serde_with",
"tokio",
"toml", "toml",
"tracing", "tracing",
"yazi-binding", "yazi-binding",
@ -5343,11 +5341,13 @@ dependencies = [
"mlua", "mlua",
"parking_lot", "parking_lot",
"russh", "russh",
"serde",
"tokio", "tokio",
"toml",
"tracing", "tracing",
"typed-path", "typed-path",
"yazi-binding", "yazi-binding",
"yazi-config", "yazi-codegen",
"yazi-fs", "yazi-fs",
"yazi-macro", "yazi-macro",
"yazi-sftp", "yazi-sftp",
@ -5396,6 +5396,7 @@ dependencies = [
"tracing", "tracing",
"unicode-width", "unicode-width",
"yazi-binding", "yazi-binding",
"yazi-dds",
"yazi-macro", "yazi-macro",
"yazi-shared", "yazi-shared",
"yazi-shim", "yazi-shim",

View file

@ -62,8 +62,8 @@ parking_lot = "0.12.5"
paste = "1.0.15" paste = "1.0.15"
percent-encoding = "2.3.2" percent-encoding = "2.3.2"
rand = { version = "0.10.1", default-features = false, features = [ "std", "sys_rng" ] } rand = { version = "0.10.1", default-features = false, features = [ "std", "sys_rng" ] }
ratatui-core = { version = "0.1.1", default-features = false, features = [ "std", "layout-cache", "serde", "underline-color" ] } ratatui-core = { version = "0.1.2", default-features = false, features = [ "std", "layout-cache", "serde", "underline-color" ] }
ratatui-widgets = { version = "0.3.1", default-features = false, features = [ "std", "unstable-rendered-line-info" ] } ratatui-widgets = { version = "0.3.2", default-features = false, features = [ "std", "unstable-rendered-line-info" ] }
regex = "1.12.4" regex = "1.12.4"
russh = { version = "0.61.2", default-features = false, features = [ "ring", "rsa" ] } russh = { version = "0.61.2", default-features = false, features = [ "ring", "rsa" ] }
scopeguard = "1.2.0" scopeguard = "1.2.0"
@ -93,7 +93,3 @@ module_inception = "allow"
option_map_unit_fn = "allow" option_map_unit_fn = "allow"
unit_arg = "allow" unit_arg = "allow"
use_self = "warn" use_self = "warn"
[patch.crates-io]
ratatui-core = { git = "https://github.com/yazi-rs/ratatui.git", package = "ratatui-core", rev = "a1c4922a242861d755163ae85d7e41b85ed277d2" }
ratatui-widgets = { git = "https://github.com/yazi-rs/ratatui.git", package = "ratatui-widgets", rev = "a1c4922a242861d755163ae85d7e41b85ed277d2" }

View file

@ -28,10 +28,6 @@ rustPlatform.buildRustPackage (finalAttrs: {
cargoLock = { cargoLock = {
lockFile = "${src}/Cargo.lock"; lockFile = "${src}/Cargo.lock";
outputHashes = {
"ratatui-core-0.1.1" = "sha256-wkEOgwAhW0ObtPDlJmOOoY5qf9c/P79ktN4b43jtcGw=";
"ratatui-widgets-0.3.1" = "sha256-wkEOgwAhW0ObtPDlJmOOoY5qf9c/P79ktN4b43jtcGw=";
};
}; };
env = { env = {

View file

@ -46,10 +46,8 @@ futures = { workspace = true }
hashbrown = { workspace = true } hashbrown = { workspace = true }
indexmap = { workspace = true } indexmap = { workspace = true }
mlua = { workspace = true } mlua = { workspace = true }
parking_lot = { workspace = true }
paste = { workspace = true } paste = { workspace = true }
ratatui-core = { workspace = true } ratatui-core = { workspace = true }
ratatui-widgets = { workspace = true }
scopeguard = { workspace = true } scopeguard = { workspace = true }
tokio = { workspace = true } tokio = { workspace = true }
tokio-stream = { workspace = true } tokio-stream = { workspace = true }

View file

@ -1,11 +1,11 @@
use std::ops::DerefMut; use std::ops::DerefMut;
use anyhow::Result; use anyhow::Result;
use ratatui_core::layout::Margin;
use yazi_config::{THEME, YAZI}; use yazi_config::{THEME, YAZI};
use yazi_macro::{act, render, succ}; use yazi_macro::{act, render, succ};
use yazi_parser::input::ShowForm; use yazi_parser::input::ShowForm;
use yazi_shared::data::Data; use yazi_shared::data::Data;
use yazi_shim::ratatui::Padable;
use crate::{Actor, Ctx}; use crate::{Actor, Ctx};
@ -19,7 +19,7 @@ impl Actor for Show {
fn act(cx: &mut Ctx, Self::Form { mut opt }: Self::Form) -> Result<Data> { fn act(cx: &mut Ctx, Self::Form { mut opt }: Self::Form) -> Result<Data> {
act!(input:close, cx)?; act!(input:close, cx)?;
let area = cx.mgr.area(opt.position); let area = cx.mgr.area(opt.position).padding(cx.input.padding());
let input = &mut cx.input; let input = &mut cx.input;
input.main.visible = true; input.main.visible = true;
input.main.title = opt.title.clone(); input.main.title = opt.title.clone();
@ -28,7 +28,7 @@ impl Actor for Show {
opt.styles = (&THEME.input).into(); opt.styles = (&THEME.input).into();
opt.blinking = YAZI.input.cursor_blink; opt.blinking = YAZI.input.cursor_blink;
*input.main.deref_mut() = yazi_widgets::input::Input::new(opt)?; *input.main.deref_mut() = yazi_widgets::input::Input::new(opt)?;
input.main.repos(area.inner(Margin::new(1, 1))); input.main.repos(area);
succ!(render!()); succ!(render!());
} }

View file

@ -71,7 +71,7 @@ impl From<ratatui_core::layout::Rect> for Area {
} }
impl From<Position> for Area { impl From<Position> for Area {
fn from(value: Position) -> Self { Self::Pos(value.into()) } fn from(value: Position) -> Self { Self::Pos(value) }
} }
impl TryFrom<&AnyUserData> for Area { impl TryFrom<&AnyUserData> for Area {

View file

@ -19,7 +19,7 @@ impl TryFrom<Table> for Position {
type Error = mlua::Error; type Error = mlua::Error;
fn try_from(t: Table) -> Result<Self, Self::Error> { fn try_from(t: Table) -> Result<Self, Self::Error> {
Ok(Self::from(Position { Ok(Self {
origin: Origin::from_str(&t.raw_get::<mlua::String>(1)?.to_str()?).into_lua_err()?, origin: Origin::from_str(&t.raw_get::<mlua::String>(1)?.to_str()?).into_lua_err()?,
offset: Offset { offset: Offset {
x: t.raw_get("x").unwrap_or_default(), x: t.raw_get("x").unwrap_or_default(),
@ -28,7 +28,7 @@ impl TryFrom<Table> for Position {
height: t.raw_get("h").unwrap_or_default(), height: t.raw_get("h").unwrap_or_default(),
}, },
padding: Default::default(), padding: Default::default(),
})) })
} }
} }

View file

@ -37,6 +37,5 @@ ratatui-widgets = { workspace = true }
regex = { workspace = true } regex = { workspace = true }
serde = { workspace = true } serde = { workspace = true }
serde_with = { workspace = true } serde_with = { workspace = true }
tokio = { workspace = true }
toml = { workspace = true } toml = { workspace = true }
tracing = { workspace = true } tracing = { workspace = true }

View file

@ -7,9 +7,8 @@ use serde_with::{DeserializeAs, DisplayFromStr, OneOrMany};
use yazi_binding::Iter; use yazi_binding::Iter;
use yazi_codegen::DeserializeOver2; use yazi_codegen::DeserializeOver2;
use yazi_shared::{Layer, event::{Actions, deserialize_actions}, id::Id}; use yazi_shared::{Layer, event::{Actions, deserialize_actions}, id::Id};
use yazi_term::event::KeyEvent;
use super::ids::chord_id; use super::{Key, ids::chord_id};
use crate::{Mixable, Platform, keymap::{ChordArc, Chords}}; use crate::{Mixable, Platform, keymap::{ChordArc, Chords}};
static RE: OnceLock<Regex> = OnceLock::new(); static RE: OnceLock<Regex> = OnceLock::new();
@ -19,7 +18,7 @@ pub struct Chord<const L: u8 = { Layer::Null as u8 }> {
#[serde(skip, default = "chord_id")] #[serde(skip, default = "chord_id")]
pub id: Id, pub id: Id,
#[serde(deserialize_with = "deserialize_on")] #[serde(deserialize_with = "deserialize_on")]
pub on: Vec<KeyEvent>, pub on: Vec<Key>,
#[serde(deserialize_with = "deserialize_actions::<L, _>")] #[serde(deserialize_with = "deserialize_actions::<L, _>")]
pub run: Actions, pub run: Actions,
#[serde(default)] #[serde(default)]
@ -84,11 +83,11 @@ impl<const L: u8> Mixable for Chord<L> {
fn filter(&self) -> bool { self.r#for.matches() && !self.noop() } fn filter(&self) -> bool { self.r#for.matches() && !self.noop() }
} }
fn deserialize_on<'de, D>(deserializer: D) -> Result<Vec<KeyEvent>, D::Error> fn deserialize_on<'de, D>(deserializer: D) -> Result<Vec<Key>, D::Error>
where where
D: Deserializer<'de>, D: Deserializer<'de>,
{ {
let keys: Vec<KeyEvent> = OneOrMany::<DisplayFromStr>::deserialize_as(deserializer)?; let keys: Vec<Key> = OneOrMany::<DisplayFromStr>::deserialize_as(deserializer)?;
if keys.is_empty() { if keys.is_empty() {
return Err(de::Error::custom("'on' cannot be empty")); return Err(de::Error::custom("'on' cannot be empty"));
} }

View file

@ -1,10 +1,44 @@
use std::{fmt::{Display, Write}, str::FromStr}; use std::{fmt::{Display, Write}, str::FromStr};
use anyhow::bail; use anyhow::bail;
use mlua::{IntoLua, Lua, LuaSerdeExt, Value};
use serde::{Deserialize, Serialize};
use yazi_shim::mlua::SER_OPT;
use yazi_term::event::{KeyCode, KeyEvent, Modifiers};
use crate::event::{KeyCode, KeyEvent, Modifiers}; #[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct Key {
#[serde(flatten)]
pub code: KeyCode,
pub shift: bool,
pub ctrl: bool,
pub alt: bool,
#[serde(rename = "super")]
pub super_: bool,
}
impl FromStr for KeyEvent { impl Key {
pub fn plain(&self) -> Option<char> {
match self.code {
KeyCode::Char(c) if !self.ctrl && !self.alt && !self.super_ => Some(c),
_ => None,
}
}
}
impl From<KeyEvent> for Key {
fn from(value: KeyEvent) -> Self {
Self {
code: value.code,
shift: value.modifiers.contains(Modifiers::SHIFT),
ctrl: value.modifiers.contains(Modifiers::CONTROL),
alt: value.modifiers.contains(Modifiers::ALT),
super_: value.modifiers.contains(Modifiers::SUPER),
}
}
}
impl FromStr for Key {
type Err = anyhow::Error; type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
@ -12,19 +46,20 @@ impl FromStr for KeyEvent {
bail!("empty key") bail!("empty key")
} }
let mut key = Self::default();
if !s.starts_with('<') || !s.ends_with('>') { if !s.starts_with('<') || !s.ends_with('>') {
let c = s.chars().next().unwrap(); key.code = KeyCode::Char(s.chars().next().unwrap());
return Ok(Self::new(KeyCode::Char(c), Modifiers::for_char(c))); key.shift = matches!(key.code, KeyCode::Char(c) if c.is_ascii_uppercase());
return Ok(key);
} }
let mut key: Self = KeyCode::Null.into();
let mut it = s[1..s.len() - 1].split_inclusive('-').peekable(); let mut it = s[1..s.len() - 1].split_inclusive('-').peekable();
while let Some(next) = it.next() { while let Some(next) = it.next() {
match next.to_ascii_lowercase().as_str() { match next.to_ascii_lowercase().as_str() {
"s-" => key.modifiers |= Modifiers::SHIFT, "s-" => key.shift = true,
"c-" => key.modifiers |= Modifiers::CONTROL, "c-" => key.ctrl = true,
"a-" => key.modifiers |= Modifiers::ALT, "a-" => key.alt = true,
"d-" => key.modifiers |= Modifiers::SUPER, "d-" => key.super_ = true,
"space" => key.code = KeyCode::Char(' '), "space" => key.code = KeyCode::Char(' '),
"backspace" => key.code = KeyCode::Backspace, "backspace" => key.code = KeyCode::Backspace,
@ -64,12 +99,8 @@ impl FromStr for KeyEvent {
_ => match next { _ => match next {
s if it.peek().is_none() => { s if it.peek().is_none() => {
let c = s.chars().next().unwrap(); let c = s.chars().next().unwrap();
key.modifiers |= Modifiers::for_char(c); key.shift |= c.is_ascii_uppercase();
key.code = KeyCode::Char(if key.modifiers.contains(Modifiers::SHIFT) { key.code = KeyCode::Char(if key.shift { c.to_ascii_uppercase() } else { c });
c.to_ascii_uppercase()
} else {
c
});
} }
s => bail!("unknown key: {s}"), s => bail!("unknown key: {s}"),
}, },
@ -83,23 +114,23 @@ impl FromStr for KeyEvent {
} }
} }
impl Display for KeyEvent { impl Display for Key {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(c) = self.plain() { if let Some(c) = self.plain() {
return if c == ' ' { write!(f, "<Space>") } else { f.write_char(c) }; return if c == ' ' { write!(f, "<Space>") } else { f.write_char(c) };
} }
write!(f, "<")?; write!(f, "<")?;
if self.modifiers.contains(Modifiers::SUPER) { if self.super_ {
write!(f, "D-")?; write!(f, "D-")?;
} }
if self.modifiers.contains(Modifiers::CONTROL) { if self.ctrl {
write!(f, "C-")?; write!(f, "C-")?;
} }
if self.modifiers.contains(Modifiers::ALT) { if self.alt {
write!(f, "A-")?; write!(f, "A-")?;
} }
if self.modifiers.contains(Modifiers::SHIFT) && !matches!(self.code, KeyCode::Char(_)) { if self.shift && !matches!(self.code, KeyCode::Char(_)) {
write!(f, "S-")?; write!(f, "S-")?;
} }
@ -149,3 +180,7 @@ impl Display for KeyEvent {
write!(f, "{code}>") write!(f, "{code}>")
} }
} }
impl IntoLua for Key {
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> { lua.to_value_with(&self, SER_OPT) }
}

View file

@ -1 +1 @@
yazi_macro::mod_flat!(chord chord_arc chords ids keymap section); yazi_macro::mod_flat!(chord chord_arc chords ids key keymap section);

View file

@ -6,9 +6,8 @@ use serde::Deserialize;
use yazi_codegen::DeserializeOver2; use yazi_codegen::DeserializeOver2;
use yazi_shared::Layer; use yazi_shared::Layer;
use yazi_shim::{mlua::UserDataFieldsExt, toml::DeserializeOverHook}; use yazi_shim::{mlua::UserDataFieldsExt, toml::DeserializeOverHook};
use yazi_term::event::KeyEvent;
use super::{Chord, Chords, chords::layer_default}; use super::{Key, Chord, Chords, chords::layer_default};
use crate::{keymap::ChordArc, mix}; use crate::{keymap::ChordArc, mix};
#[derive(Default, Deserialize, DeserializeOver2)] #[derive(Default, Deserialize, DeserializeOver2)]
@ -37,7 +36,7 @@ impl<const L: u8> KeymapSection<L> {
impl<const L: u8> DeserializeOverHook for KeymapSection<L> { impl<const L: u8> DeserializeOverHook for KeymapSection<L> {
fn deserialize_over_hook(self) -> Result<Self, toml::de::Error> { fn deserialize_over_hook(self) -> Result<Self, toml::de::Error> {
#[inline] #[inline]
fn on<const L: u8>(Chord { on, .. }: &Chord<L>) -> [KeyEvent; 2] { fn on<const L: u8>(Chord { on, .. }: &Chord<L>) -> [Key; 2] {
[on.first().copied().unwrap_or_default(), on.get(1).copied().unwrap_or_default()] [on.first().copied().unwrap_or_default(), on.get(1).copied().unwrap_or_default()]
} }

View file

@ -1,6 +1,6 @@
yazi_macro::mod_pub!(keymap mgr open opener plugin popup preview tasks theme vfs which); yazi_macro::mod_pub!(keymap mgr open opener plugin popup preview tasks theme which);
yazi_macro::mod_flat!(icon inject layout mixing pattern platform preset priority selectable selector utils yazi); yazi_macro::mod_flat!(icon inject layout mixing pattern platform preset priority selectable selector yazi);
use std::io::{Read, Write}; use std::io::{Read, Write};

View file

@ -1,4 +1,4 @@
use crate::{Yazi, keymap::Keymap, theme::Theme, vfs::Vfs}; use crate::{Yazi, keymap::Keymap, theme::Theme};
pub(crate) struct Preset; pub(crate) struct Preset;
@ -18,8 +18,4 @@ impl Preset {
yazi_macro::theme_preset!("dark") yazi_macro::theme_preset!("dark")
}) })
} }
pub(super) fn vfs() -> Result<Vfs, toml::de::Error> {
toml::from_str(&yazi_macro::config_preset!("vfs"))
}
} }

View file

@ -3,12 +3,11 @@ use std::path::PathBuf;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use serde::{Deserialize, Deserializer, Serialize}; use serde::{Deserialize, Deserializer, Serialize};
use yazi_codegen::DeserializeOver2; use yazi_codegen::DeserializeOver2;
use yazi_fs::{Xdg, create_owned_dir_blocking}; use yazi_fs::{Xdg, create_owned_dir_blocking, path::sanitize_path};
use yazi_shared::timestamp_us; use yazi_shared::timestamp_us;
use yazi_shim::{SStr, toml::DeserializeOverHook}; use yazi_shim::{SStr, toml::DeserializeOverHook};
use super::PreviewWrap; use super::PreviewWrap;
use crate::normalize_path;
#[derive(Debug, Deserialize, DeserializeOver2, Serialize)] #[derive(Debug, Deserialize, DeserializeOver2, Serialize)]
pub struct Preview { pub struct Preview {
@ -65,7 +64,7 @@ where
if path.as_os_str().is_empty() { if path.as_os_str().is_empty() {
Ok(Xdg::temp_dir().to_owned()) Ok(Xdg::temp_dir().to_owned())
} else { } else {
normalize_path(path).ok_or_else(|| { sanitize_path(path).ok_or_else(|| {
serde::de::Error::custom("cache_dir must be either empty or an absolute path.") serde::de::Error::custom("cache_dir must be either empty or an absolute path.")
}) })
} }

View file

@ -5,11 +5,10 @@ use arc_swap::ArcSwap;
use serde::{Deserialize, Deserializer, de}; use serde::{Deserialize, Deserializer, de};
use yazi_binding::style::StyleFlat; use yazi_binding::style::StyleFlat;
use yazi_codegen::{DeserializeOver, DeserializeOver1, DeserializeOver2, Overlay}; use yazi_codegen::{DeserializeOver, DeserializeOver1, DeserializeOver2, Overlay};
use yazi_fs::{Xdg, ok_or_not_found}; use yazi_fs::{Xdg, ok_or_not_found, path::sanitize_path};
use yazi_shim::{arc_swap::IntoPointee, cell::SyncCell}; use yazi_shim::{arc_swap::IntoPointee, cell::SyncCell};
use super::{Custom, Filetype, Flavor, Icon}; use super::{Custom, Filetype, Flavor, Icon};
use crate::normalize_path;
#[derive(Deserialize, DeserializeOver, DeserializeOver1, Overlay)] #[derive(Deserialize, DeserializeOver, DeserializeOver1, Overlay)]
pub struct Theme { pub struct Theme {
@ -274,7 +273,7 @@ where
{ {
let mut path = PathBuf::deserialize(deserializer)?; let mut path = PathBuf::deserialize(deserializer)?;
if !path.as_os_str().is_empty() { if !path.as_os_str().is_empty() {
path = normalize_path(path).ok_or_else(|| { path = sanitize_path(path).ok_or_else(|| {
de::Error::custom("syntect_theme must be either empty or an absolute path.") de::Error::custom("syntect_theme must be either empty or an absolute path.")
})?; })?;
} }

View file

@ -1,5 +1,6 @@
use ratatui_core::layout::{Margin, Position, Rect}; use ratatui_core::layout::{Position, Rect};
use yazi_shared::Layer; use yazi_shared::Layer;
use yazi_shim::ratatui::Padable;
use yazi_tty::sequence::SetCursorStyle; use yazi_tty::sequence::SetCursorStyle;
use crate::{cmp::Cmp, confirm::Confirm, help::Help, input::{Input, InputGuard}, mgr::Mgr, notify::Notify, pick::Pick, tab::{Folder, Tab}, tasks::Tasks, which::Which}; use crate::{cmp::Cmp, confirm::Confirm, help::Help, input::{Input, InputGuard}, mgr::Mgr, notify::Notify, pick::Pick, tab::{Folder, Tab}, tasks::Tasks, which::Which};
@ -34,7 +35,7 @@ impl Core {
pub fn cursor(&self) -> Option<(Position, SetCursorStyle)> { pub fn cursor(&self) -> Option<(Position, SetCursorStyle)> {
if let Some(guard) = self.input.lock() { if let Some(guard) = self.input.lock() {
let Rect { x, y, .. } = match &guard { let Rect { x, y, .. } = match &guard {
InputGuard::Main(_) => self.mgr.area(self.input.position()?).inner(Margin::new(1, 1)), InputGuard::Main(_) => self.mgr.area(self.input.position()?).padding(self.input.padding()),
InputGuard::Alt(_) => self.mgr.area(self.input.position()?), InputGuard::Alt(_) => self.mgr.area(self.input.position()?),
}; };
return Some((Position { x: x + guard.cursor(), y }, guard.cursor_shape())); return Some((Position { x: x + guard.cursor(), y }, guard.cursor_shape()));

View file

@ -1,6 +1,7 @@
use std::{ops::{Deref, DerefMut}, sync::Arc}; use std::{ops::{Deref, DerefMut}, sync::Arc};
use parking_lot::Mutex; use parking_lot::Mutex;
use ratatui_widgets::block::Padding;
use yazi_binding::{elements::Spatial, position::Position}; use yazi_binding::{elements::Spatial, position::Position};
use crate::input::{InputGuard, InputMutGuard}; use crate::input::{InputGuard, InputMutGuard};
@ -14,6 +15,8 @@ pub struct Input {
impl Input { impl Input {
pub fn focus(&self) -> bool { self.main.visible || self.alt.is_some() } pub fn focus(&self) -> bool { self.main.visible || self.alt.is_some() }
pub fn padding(&self) -> Padding { Padding::new(1, 1, 1, 1) }
pub fn position(&self) -> Option<Position> { pub fn position(&self) -> Option<Position> {
if self.main.visible { if self.main.visible {
Some(self.main.position) Some(self.main.position)

View file

@ -1,9 +1,8 @@
use mlua::{ExternalError, FromLua, IntoLua, Lua, Table, Value}; use mlua::{ExternalError, FromLua, IntoLua, Lua, Table, Value};
use tokio::sync::mpsc; use tokio::sync::mpsc;
use yazi_config::{KEYMAP, keymap::ChordArc}; use yazi_config::{KEYMAP, keymap::{ChordArc, Key}};
use yazi_macro::impl_data_any; use yazi_macro::impl_data_any;
use yazi_shared::{Layer, event::ActionCow}; use yazi_shared::{Layer, event::ActionCow};
use yazi_term::event::KeyEvent;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct WhichOpt { pub struct WhichOpt {
@ -32,8 +31,8 @@ impl TryFrom<ActionCow> for WhichOpt {
} }
} }
impl From<(Layer, KeyEvent)> for WhichOpt { impl From<(Layer, Key)> for WhichOpt {
fn from((layer, key): (Layer, KeyEvent)) -> Self { fn from((layer, key): (Layer, Key)) -> Self {
Self { Self {
tx: None, tx: None,
cands: KEYMAP cands: KEYMAP

View file

@ -1,7 +1,6 @@
use tokio::sync::mpsc; use tokio::sync::mpsc;
use yazi_config::keymap::ChordArc; use yazi_config::keymap::{ChordArc, Key};
use yazi_macro::{emit, render_and}; use yazi_macro::{emit, render_and};
use yazi_term::event::KeyEvent;
#[derive(Default)] #[derive(Default)]
pub struct Which { pub struct Which {
@ -15,7 +14,7 @@ pub struct Which {
} }
impl Which { impl Which {
pub fn r#type(&mut self, key: KeyEvent) -> bool { pub fn r#type(&mut self, key: Key) -> bool {
self.cands.retain(|c| c.on.len() > self.times && c.on[self.times] == key); self.cands.retain(|c| c.on.len() > self.times && c.on[self.times] == key);
self.times += 1; self.times += 1;

View file

@ -1,6 +1,6 @@
use anyhow::Result; use anyhow::Result;
use yazi_actor::Ctx; use yazi_actor::Ctx;
use yazi_config::{KEYMAP, keymap::Chord}; use yazi_config::{KEYMAP, keymap::{Chord, Key}};
use yazi_macro::act; use yazi_macro::act;
use yazi_shared::Layer; use yazi_shared::Layer;
use yazi_term::event::KeyEvent; use yazi_term::event::KeyEvent;
@ -29,6 +29,7 @@ impl<'a> Router<'a> {
} }
let layer = core.layer(); let layer = core.layer();
let key = Key::from(key);
Ok(match layer { Ok(match layer {
L::Null | L::App | L::Notify => unreachable!(), L::Null | L::App | L::Notify => unreachable!(),
L::Mgr | L::Tasks | L::Spot | L::Pick | L::Input | L::Confirm | L::Help => { L::Mgr | L::Tasks | L::Spot | L::Pick | L::Input | L::Confirm | L::Help => {
@ -39,7 +40,7 @@ impl<'a> Router<'a> {
}) })
} }
fn matches(&mut self, layer: Layer, key: KeyEvent) -> bool { fn matches(&mut self, layer: Layer, key: Key) -> bool {
for chord in &*KEYMAP.chords(layer) { for chord in &*KEYMAP.chords(layer) {
let Chord { on, .. } = chord.as_ref(); let Chord { on, .. } = chord.as_ref();
if on.is_empty() || on[0] != key { if on.is_empty() || on[0] != key {

View file

@ -1 +1 @@
yazi_macro::mod_flat!(clean expand path percent relative); yazi_macro::mod_flat!(clean expand normalize path percent relative);

View file

@ -1,9 +1,10 @@
use std::path::PathBuf; use std::path::PathBuf;
use yazi_fs::path::{clean_url, expand_url};
use yazi_shared::url::UrlBuf; use yazi_shared::url::UrlBuf;
pub(crate) fn normalize_path(path: PathBuf) -> Option<PathBuf> { use crate::path::{clean_url, expand_url};
pub fn sanitize_path(path: PathBuf) -> Option<PathBuf> {
clean_url(yazi_fs::provider::local::try_absolute(expand_url(UrlBuf::from(path)))?) clean_url(yazi_fs::provider::local::try_absolute(expand_url(UrlBuf::from(path)))?)
.into_loc() .into_loc()
.into_os() .into_os()

View file

@ -6,7 +6,7 @@ macro_rules! config_preset {
std::borrow::Cow::from( std::borrow::Cow::from(
std::fs::read_to_string(concat!( std::fs::read_to_string(concat!(
env!("CARGO_MANIFEST_DIR"), env!("CARGO_MANIFEST_DIR"),
"/preset/", "/../yazi-config/preset/",
$name, $name,
"-default.toml" "-default.toml"
)) ))
@ -17,7 +17,7 @@ macro_rules! config_preset {
{ {
std::borrow::Cow::from(include_str!(concat!( std::borrow::Cow::from(include_str!(concat!(
env!("CARGO_MANIFEST_DIR"), env!("CARGO_MANIFEST_DIR"),
"/preset/", "/../yazi-config/preset/",
$name, $name,
"-default.toml" "-default.toml"
))) )))

View file

@ -1,15 +1,15 @@
use mlua::{FromLua, IntoLua, Lua, Value}; use mlua::{FromLua, IntoLua, Lua, Value};
use yazi_config::keymap::Key;
use yazi_core::which::WhichOpt; use yazi_core::which::WhichOpt;
use yazi_shared::{Layer, event::ActionCow}; use yazi_shared::{Layer, event::ActionCow};
use yazi_term::event::KeyEvent;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct ActivateForm { pub struct ActivateForm {
pub opt: WhichOpt, pub opt: WhichOpt,
} }
impl From<(Layer, KeyEvent)> for ActivateForm { impl From<(Layer, Key)> for ActivateForm {
fn from(value: (Layer, KeyEvent)) -> Self { Self { opt: value.into() } } fn from(value: (Layer, Key)) -> Self { Self { opt: value.into() } }
} }
impl TryFrom<ActionCow> for ActivateForm { impl TryFrom<ActionCow> for ActivateForm {

View file

@ -3,12 +3,11 @@ use std::{str::FromStr, time::Duration};
use mlua::{ExternalError, ExternalResult, Function, IntoLuaMulti, Lua, Table, Value}; use mlua::{ExternalError, ExternalResult, Function, IntoLuaMulti, Lua, Table, Value};
use tokio_stream::wrappers::UnboundedReceiverStream; use tokio_stream::wrappers::UnboundedReceiverStream;
use yazi_binding::{elements::{Line, Text}, runtime}; use yazi_binding::{elements::{Line, Text}, runtime};
use yazi_config::{Platform, keymap::{Chord, ChordArc}, popup::ConfirmCfg}; use yazi_config::{Platform, keymap::{Chord, ChordArc, Key}, popup::ConfirmCfg};
use yazi_core::notify::MessageOpt; use yazi_core::notify::MessageOpt;
use yazi_macro::relay; use yazi_macro::relay;
use yazi_proxy::{ConfirmProxy, InputProxy, NotifyProxy, WhichProxy}; use yazi_proxy::{ConfirmProxy, InputProxy, NotifyProxy, WhichProxy};
use yazi_shared::{Debounce, Layer}; use yazi_shared::{Debounce, Layer};
use yazi_term::event::KeyEvent;
use yazi_widgets::input::{InputOpt, InputStream}; use yazi_widgets::input::{InputOpt, InputStream};
use super::Utils; use super::Utils;
@ -93,15 +92,15 @@ impl Utils {
lua.create_function(|_, opt: MessageOpt| Ok(NotifyProxy::push(opt))) lua.create_function(|_, opt: MessageOpt| Ok(NotifyProxy::push(opt)))
} }
fn parse_keys(value: Value) -> mlua::Result<Vec<KeyEvent>> { fn parse_keys(value: Value) -> mlua::Result<Vec<Key>> {
Ok(match value { Ok(match value {
Value::String(s) => { Value::String(s) => {
vec![KeyEvent::from_str(&s.to_str()?).into_lua_err()?] vec![Key::from_str(&s.to_str()?).into_lua_err()?]
} }
Value::Table(t) => { Value::Table(t) => {
let mut v = Vec::with_capacity(10); let mut v = Vec::with_capacity(10);
for s in t.sequence_values::<mlua::String>() { for s in t.sequence_values::<mlua::String>() {
v.push(KeyEvent::from_str(&s?.to_str()?).into_lua_err()?); v.push(Key::from_str(&s?.to_str()?).into_lua_err()?);
} }
v v
} }

View file

@ -1 +1 @@
yazi_macro::mod_flat!(conversion dnd event keyboard lua modifiers mouse); yazi_macro::mod_flat!(dnd event keyboard lua modifiers mouse);

View file

@ -14,7 +14,7 @@ workspace = true
[dependencies] [dependencies]
yazi-binding = { path = "../yazi-binding", version = "26.5.6" } yazi-binding = { path = "../yazi-binding", version = "26.5.6" }
yazi-config = { path = "../yazi-config", version = "26.5.6" } yazi-codegen = { path = "../yazi-codegen", version = "26.5.6" }
yazi-fs = { path = "../yazi-fs", version = "26.5.6" } yazi-fs = { path = "../yazi-fs", version = "26.5.6" }
yazi-macro = { path = "../yazi-macro", version = "26.5.6" } yazi-macro = { path = "../yazi-macro", version = "26.5.6" }
yazi-sftp = { path = "../yazi-sftp", version = "26.5.6" } yazi-sftp = { path = "../yazi-sftp", version = "26.5.6" }
@ -31,6 +31,8 @@ hashbrown = { workspace = true }
mlua = { workspace = true } mlua = { workspace = true }
parking_lot = { workspace = true } parking_lot = { workspace = true }
russh = { workspace = true } russh = { workspace = true }
serde = { workspace = true }
tokio = { workspace = true } tokio = { workspace = true }
toml = { workspace = true }
tracing = { workspace = true } tracing = { workspace = true }
typed-path = { workspace = true } typed-path = { workspace = true }

View file

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::vfs::ServiceSftp; use crate::config::ServiceSftp;
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
#[serde(tag = "type", rename_all = "kebab-case")] #[serde(tag = "type", rename_all = "kebab-case")]

View file

@ -6,7 +6,7 @@ use serde::{Deserialize, Deserializer, Serialize, de, de::{MapAccess, Visitor}};
use yazi_codegen::DeserializeOver; use yazi_codegen::DeserializeOver;
use yazi_shim::toml::DeserializeOverWith; use yazi_shim::toml::DeserializeOverWith;
use crate::vfs::Service; use crate::config::Service;
#[derive(Serialize, Deserialize, DeserializeOver)] #[derive(Serialize, Deserialize, DeserializeOver)]
pub struct Services(HashMap<String, Service>); pub struct Services(HashMap<String, Service>);

View file

@ -1,8 +1,7 @@
use std::{env, path::PathBuf}; use std::{env, path::PathBuf};
use serde::{Deserialize, Deserializer, Serialize, de}; use serde::{Deserialize, Deserializer, Serialize, de};
use yazi_fs::path::sanitize_path;
use crate::normalize_path;
#[derive(Deserialize, Eq, Hash, PartialEq, Serialize)] #[derive(Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct ServiceSftp { pub struct ServiceSftp {
@ -27,7 +26,7 @@ where
{ {
let mut path = PathBuf::deserialize(deserializer)?; let mut path = PathBuf::deserialize(deserializer)?;
if !path.as_os_str().is_empty() { if !path.as_os_str().is_empty() {
path = normalize_path(path) path = sanitize_path(path)
.ok_or_else(|| de::Error::custom("path must be either empty or an absolute path"))?; .ok_or_else(|| de::Error::custom("path must be either empty or an absolute path"))?;
} }
@ -46,7 +45,7 @@ where
if path.as_os_str().is_empty() { if path.as_os_str().is_empty() {
Ok(default_identity_agent()) Ok(default_identity_agent())
} else { } else {
normalize_path(path) sanitize_path(path)
.ok_or_else(|| de::Error::custom("identity_agent must be either empty or an absolute path")) .ok_or_else(|| de::Error::custom("identity_agent must be either empty or an absolute path"))
} }
} }

View file

@ -8,7 +8,7 @@ use yazi_fs::{Xdg, ok_or_not_found};
use yazi_shim::toml::DeserializeOver; use yazi_shim::toml::DeserializeOver;
use super::Service; use super::Service;
use crate::{Preset, vfs::Services}; use crate::config::Services;
#[derive(Deserialize, Serialize, DeserializeOver, DeserializeOver1)] #[derive(Deserialize, Serialize, DeserializeOver, DeserializeOver1)]
pub struct Vfs { pub struct Vfs {
@ -21,7 +21,8 @@ impl Vfs {
async fn init() -> io::Result<Vfs> { async fn init() -> io::Result<Vfs> {
tokio::task::spawn_blocking(|| -> anyhow::Result<Vfs> { tokio::task::spawn_blocking(|| -> anyhow::Result<Vfs> {
Ok(Preset::vfs()?.deserialize_over(&Vfs::read()?)?) let vfs: Vfs = toml::from_str(&yazi_macro::config_preset!("vfs"))?;
Ok(vfs.deserialize_over(&Vfs::read()?)?)
}) })
.await? .await?
.map_err(io::Error::other) .map_err(io::Error::other)

View file

@ -1,4 +1,4 @@
yazi_macro::mod_pub!(provider); yazi_macro::mod_pub!(config provider);
yazi_macro::mod_flat!(cha file files fns op); yazi_macro::mod_flat!(cha file files fns op);

View file

@ -2,9 +2,10 @@ use std::{io, sync::Arc, time::{Duration, SystemTime}};
use chrono::DateTime; use chrono::DateTime;
use russh::keys::{PrivateKeyWithHashAlg, agent::AgentIdentity}; use russh::keys::{PrivateKeyWithHashAlg, agent::AgentIdentity};
use yazi_config::vfs::ServiceSftp;
use yazi_fs::provider::local::Local; use yazi_fs::provider::local::Local;
use crate::config::ServiceSftp;
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub(super) struct Conn { pub(super) struct Conn {
pub(super) name: &'static str, pub(super) name: &'static str,

View file

@ -1,11 +1,10 @@
use std::io; use std::io;
use yazi_config::vfs::{ServiceSftp, Vfs};
use yazi_fs::provider::{Attrs, FileBuilder}; use yazi_fs::provider::{Attrs, FileBuilder};
use yazi_sftp::fs::Flags; use yazi_sftp::fs::Flags;
use yazi_shared::url::{AsUrl, Url}; use yazi_shared::url::{AsUrl, Url};
use crate::provider::sftp::Conn; use crate::{config::{ServiceSftp, Vfs}, provider::sftp::Conn};
#[derive(Clone, Copy, Default)] #[derive(Clone, Copy, Default)]
pub struct Gate(crate::provider::Gate); pub struct Gate(crate::provider::Gate);

View file

@ -2,10 +2,7 @@ yazi_macro::mod_flat!(absolute conn gate metadata read_dir sftp);
static CONN: yazi_shim::cell::RoCell< static CONN: yazi_shim::cell::RoCell<
parking_lot::Mutex< parking_lot::Mutex<
hashbrown::HashMap< hashbrown::HashMap<&'static crate::config::ServiceSftp, &'static deadpool::managed::Pool<Conn>>,
&'static yazi_config::vfs::ServiceSftp,
&'static deadpool::managed::Pool<Conn>,
>,
>, >,
> = yazi_shim::cell::RoCell::new(); > = yazi_shim::cell::RoCell::new();

View file

@ -1,13 +1,12 @@
use std::{io, sync::Arc}; use std::{io, sync::Arc};
use tokio::{io::{AsyncWriteExt, BufReader, BufWriter}, sync::mpsc::Receiver}; use tokio::{io::{AsyncWriteExt, BufReader, BufWriter}, sync::mpsc::Receiver};
use yazi_config::vfs::{ServiceSftp, Vfs};
use yazi_fs::{cha::ChaMode, provider::{Capabilities, DirReader, FileHolder, Provider}}; use yazi_fs::{cha::ChaMode, provider::{Capabilities, DirReader, FileHolder, Provider}};
use yazi_sftp::fs::{Attrs, Flags}; use yazi_sftp::fs::{Attrs, Flags};
use yazi_shared::{loc::LocBuf, path::{AsPath, PathBufDyn}, pool::InternStr, scheme::SchemeKind, strand::AsStrand, url::{Url, UrlBuf, UrlCow, UrlLike}}; use yazi_shared::{loc::LocBuf, path::{AsPath, PathBufDyn}, pool::InternStr, scheme::SchemeKind, strand::AsStrand, url::{Url, UrlBuf, UrlCow, UrlLike}};
use super::Cha; use super::Cha;
use crate::provider::sftp::Conn; use crate::{config::{ServiceSftp, Vfs}, provider::sftp::Conn};
#[derive(Clone)] #[derive(Clone)]
pub struct Sftp<'a> { pub struct Sftp<'a> {

View file

@ -18,6 +18,7 @@ vendored-lua = [ "mlua/vendored" ]
[dependencies] [dependencies]
yazi-binding = { path = "../yazi-binding", version = "26.5.6" } yazi-binding = { path = "../yazi-binding", version = "26.5.6" }
yazi-dds = { path = "../yazi-dds", version = "26.5.6" }
yazi-macro = { path = "../yazi-macro", version = "26.5.6" } yazi-macro = { path = "../yazi-macro", version = "26.5.6" }
yazi-shared = { path = "../yazi-shared", version = "26.5.6" } yazi-shared = { path = "../yazi-shared", version = "26.5.6" }
yazi-shim = { path = "../yazi-shim", version = "26.5.6" } yazi-shim = { path = "../yazi-shim", version = "26.5.6" }

View file

@ -4,6 +4,9 @@ use mlua::{AnyUserData, IntoLua, Lua, MetaMethod, Table, UserData, UserDataMetho
use parking_lot::Mutex; use parking_lot::Mutex;
use ratatui_core::widgets::Widget; use ratatui_core::widgets::Widget;
use yazi_binding::{elements::{Area, Spatial}, impl_area_method}; use yazi_binding::{elements::{Area, Spatial}, impl_area_method};
use yazi_dds::Pubsub;
use yazi_macro::err;
use yazi_shim::strum::IntoStr;
use crate::input::{Input, InputOpt, InputStyles}; use crate::input::{Input, InputOpt, InputStyles};
@ -26,6 +29,7 @@ impl InputArc {
let new = lua.create_function(move |_, (_, mut opt): (Table, InputOpt)| { let new = lua.create_function(move |_, (_, mut opt): (Table, InputOpt)| {
opt.styles.normal = opt.styles.normal.or(styles.normal); opt.styles.normal = opt.styles.normal.or(styles.normal);
opt.styles.selected = opt.styles.selected.or(styles.selected); opt.styles.selected = opt.styles.selected.or(styles.selected);
opt = opt.with_cb(|e| err!(Pubsub::pub_after_input((&e).into_str(), e.value())));
Ok(Self::from(Input::new(opt)?)) Ok(Self::from(Input::new(opt)?))
})?; })?;