mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 14:51:03 +00:00
feat!: make help menu a command palette (#4074)
This commit is contained in:
parent
4f45ddb514
commit
581886a355
48 changed files with 399 additions and 380 deletions
2
.github/workflows/cachix.yml
vendored
2
.github/workflows/cachix.yml
vendored
|
|
@ -15,7 +15,7 @@ jobs:
|
|||
os: [ubuntu-latest, macos-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- name: Install Nix
|
||||
uses: cachix/install-nix-action@v31
|
||||
|
|
|
|||
6
.github/workflows/check.yml
vendored
6
.github/workflows/check.yml
vendored
|
|
@ -17,7 +17,7 @@ jobs:
|
|||
clippy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
run: |
|
||||
|
|
@ -34,7 +34,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
run: |
|
||||
|
|
@ -50,7 +50,7 @@ jobs:
|
|||
stylua:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
- uses: JohnnyMorganz/stylua-action@v5
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
|
|
|||
10
.github/workflows/draft.yml
vendored
10
.github/workflows/draft.yml
vendored
|
|
@ -42,7 +42,7 @@ jobs:
|
|||
CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_LINKER: riscv64-linux-gnu-gcc
|
||||
CARGO_TARGET_SPARC64_UNKNOWN_LINUX_GNU_LINKER: sparc64-linux-gnu-gcc
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- name: Install gcc
|
||||
if: matrix.gcc != ''
|
||||
|
|
@ -81,7 +81,7 @@ jobs:
|
|||
CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER: lld-link.exe
|
||||
CARGO_TARGET_AARCH64_PC_WINDOWS_MSVC_LINKER: lld-link.exe
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
|
|
@ -120,7 +120,7 @@ jobs:
|
|||
container:
|
||||
image: docker://ghcr.io/cross-rs/${{ matrix.target }}:edge
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
|
|
@ -150,7 +150,7 @@ jobs:
|
|||
arch: arm64
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
|
@ -233,7 +233,7 @@ jobs:
|
|||
echo "Nightly changelog: https://github.com/sxyazi/yazi/blob/main/CHANGELOG.md#unreleased" >> $GITHUB_ENV
|
||||
echo "EOF" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
|
|
|
|||
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
|
|
@ -21,7 +21,7 @@ jobs:
|
|||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
run: rustup toolchain install stable --profile minimal
|
||||
|
|
|
|||
2
.github/workflows/validate-form.yml
vendored
2
.github/workflows/validate-form.yml
vendored
|
|
@ -12,7 +12,7 @@ jobs:
|
|||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
|
||||
- Drag and drop ([#4005])
|
||||
- Bulk create ([#3793])
|
||||
- Make help menu a command palette ([#4074])
|
||||
- Dynamic keymap Lua API ([#4031])
|
||||
- New `ui.Input` element ([#4040])
|
||||
- Image preview with Überzug++ on Niri ([#3990])
|
||||
|
|
@ -24,6 +25,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
### Changed
|
||||
|
||||
- Rename `<BackTab>` to `<S-Tab>` ([#3989])
|
||||
- Remove `help:filter` action since the filter input is now always available ([#4074])
|
||||
- `[help]` of `theme.toml`: supersede `on` with `chord`, supersede `run` and `desc` with `action`, remove `footer` ([#4074])
|
||||
- Remove Legacy Console Mode on Windows ([#3989])
|
||||
|
||||
### Deprecated
|
||||
|
|
@ -1757,3 +1760,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
[#4065]: https://github.com/sxyazi/yazi/pull/4065
|
||||
[#4067]: https://github.com/sxyazi/yazi/pull/4067
|
||||
[#4068]: https://github.com/sxyazi/yazi/pull/4068
|
||||
[#4074]: https://github.com/sxyazi/yazi/pull/4074
|
||||
|
|
|
|||
20
Cargo.lock
generated
20
Cargo.lock
generated
|
|
@ -215,9 +215,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.6"
|
||||
version = "0.7.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||
checksum = "f02882884d3e1bc524fb12c79f107f6ad0e1cfd498c536ffb494301740995dfe"
|
||||
|
||||
[[package]]
|
||||
name = "as-slice"
|
||||
|
|
@ -1931,9 +1931,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.32"
|
||||
version = "0.4.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a"
|
||||
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
|
||||
|
||||
[[package]]
|
||||
name = "loop9"
|
||||
|
|
@ -2788,9 +2788,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.45"
|
||||
version = "1.0.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
||||
checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
|
@ -3831,9 +3831,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.49"
|
||||
version = "0.3.51"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "711a53c2d47bbd818258c498c8dbfe186a2526c631495cfe7e078567f86b8469"
|
||||
checksum = "85c17d80feb7334b40c484e45ed1a5273dfd8bfda537c3be2e74a06a6686f327"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"libc",
|
||||
|
|
@ -3853,9 +3853,9 @@ checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109"
|
|||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.29"
|
||||
version = "0.2.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71c652a3727a9cbb9a02f707f530b618ce00d0ccd762009c8c23bd191df3c17d"
|
||||
checksum = "dcef1a61bdb119096e153208ec5cbec23944ce8bca13be5c7f60c634f7403935"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
|
|
|
|||
25
yazi-actor/src/help/close.rs
Normal file
25
yazi-actor/src/help/close.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{emit, render, succ};
|
||||
use yazi_parser::help::CloseForm;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Close;
|
||||
|
||||
impl Actor for Close {
|
||||
type Form = CloseForm;
|
||||
|
||||
const NAME: &str = "close";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
|
||||
let help = &mut cx.help;
|
||||
|
||||
if let Some(chord) = help.bindings.get(help.cursor).filter(|_| opt.submit) {
|
||||
emit!(Seq(chord.to_seq(help.layer)));
|
||||
}
|
||||
|
||||
help.visible = false;
|
||||
succ!(render!());
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ use anyhow::Result;
|
|||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::VoidForm;
|
||||
use yazi_shared::data::Data;
|
||||
use yazi_widgets::input::InputMode;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
|
|
@ -13,15 +14,11 @@ impl Actor for Escape {
|
|||
const NAME: &str = "escape";
|
||||
|
||||
fn act(cx: &mut Ctx, _: Self::Form) -> Result<Data> {
|
||||
if cx.help.keyword().is_none() {
|
||||
return act!(help:toggle, cx, cx.help.layer);
|
||||
if cx.help.input.mode() == InputMode::Normal {
|
||||
return act!(help:close, cx);
|
||||
}
|
||||
|
||||
let help = &mut cx.help;
|
||||
help.keyword = String::new();
|
||||
help.in_filter = None;
|
||||
help.filter_apply();
|
||||
|
||||
act!(escape, cx.help.input)?;
|
||||
succ!(render!());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +0,0 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{render, succ};
|
||||
use yazi_parser::VoidForm;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Filter;
|
||||
|
||||
impl Actor for Filter {
|
||||
type Form = VoidForm;
|
||||
|
||||
const NAME: &str = "filter";
|
||||
|
||||
fn act(cx: &mut Ctx, _: Self::Form) -> Result<Data> {
|
||||
let help = &mut cx.help;
|
||||
|
||||
help.in_filter = Some(Default::default());
|
||||
help.filter_apply();
|
||||
succ!(render!());
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +1 @@
|
|||
yazi_macro::mod_flat!(arrow escape filter toggle);
|
||||
yazi_macro::mod_flat!(arrow close escape toggle);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
use anyhow::Result;
|
||||
use ratatui_core::layout::Margin;
|
||||
use yazi_config::popup::Help;
|
||||
use yazi_macro::{render, succ};
|
||||
use yazi_parser::help::ToggleForm;
|
||||
use yazi_shared::data::Data;
|
||||
use yazi_widgets::input::Input;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
|
|
@ -13,17 +16,24 @@ impl Actor for Toggle {
|
|||
const NAME: &str = "toggle";
|
||||
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
let position = Help::position();
|
||||
let area = cx.mgr.area(position);
|
||||
let input_area = area.inner(Margin::new(1, 1));
|
||||
|
||||
let help = &mut cx.help;
|
||||
|
||||
help.visible = !help.visible;
|
||||
help.visible = true;
|
||||
help.layer = form.layer;
|
||||
help.position = position;
|
||||
help.height = area.height;
|
||||
|
||||
help.keyword = String::new();
|
||||
help.in_filter = None;
|
||||
help.filter_apply();
|
||||
help.input = Input::default();
|
||||
help.input.repos(input_area);
|
||||
|
||||
help.keyword.clear();
|
||||
help.offset = 0;
|
||||
help.cursor = 0;
|
||||
help.filter_apply();
|
||||
|
||||
succ!(render!());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,10 +24,10 @@ impl Which {
|
|||
impl UserData for Which {
|
||||
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_cached_field("tx", |_, me| Ok(me.tx.clone().map(yazi_binding::MpscUnboundedTx)));
|
||||
fields.add_field_method_get("times", |_, me| Ok(me.inner.times));
|
||||
fields.add_cached_field("cands", |lua, me| {
|
||||
lua.create_sequence_from(me.inner.cands.iter().cloned())
|
||||
});
|
||||
fields.add_field_method_get("times", |_, me| Ok(me.inner.times));
|
||||
|
||||
fields.add_field_method_get("active", |_, me| Ok(me.inner.active));
|
||||
fields.add_field_method_get("silent", |_, me| Ok(me.inner.silent));
|
||||
|
|
|
|||
|
|
@ -23,8 +23,9 @@ impl Actor for Activate {
|
|||
|
||||
let which = &mut cx.which;
|
||||
which.tx = opt.tx;
|
||||
which.times = opt.times;
|
||||
which.layer = opt.layer;
|
||||
which.cands = opt.cands;
|
||||
which.times = opt.times;
|
||||
|
||||
which.active = true;
|
||||
which.silent = opt.silent;
|
||||
|
|
|
|||
|
|
@ -18,5 +18,5 @@ proc-macro = true
|
|||
[dependencies]
|
||||
# External dependencies
|
||||
proc-macro2 = "1"
|
||||
quote = "1.0.45"
|
||||
quote = "1.0.46"
|
||||
syn = { version = "2.0.118", features = [ "full" ] }
|
||||
|
|
|
|||
|
|
@ -360,9 +360,10 @@ keymap = [
|
|||
[help]
|
||||
|
||||
keymap = [
|
||||
{ on = "<Esc>", run = "escape", desc = "Clear the filter, or hide the help" },
|
||||
{ on = "<C-[>", run = "escape", desc = "Clear the filter, or hide the help" },
|
||||
{ on = "<C-c>", run = "close", desc = "Hide the help" },
|
||||
{ on = "<Esc>", run = "escape", desc = "Enter normal mode, or hide help menu" },
|
||||
{ on = "<C-[>", run = "escape", desc = "Enter normal mode, or hide help menu" },
|
||||
{ on = "<C-c>", run = "close", desc = "Close help menu" },
|
||||
{ on = "<Enter>", run = "close --submit", desc = "Close help menu and run selected action(s)" },
|
||||
|
||||
# Navigation
|
||||
{ on = "k", run = "arrow prev", desc = "Previous line" },
|
||||
|
|
@ -371,6 +372,6 @@ keymap = [
|
|||
{ on = "<Up>", run = "arrow prev", desc = "Previous line" },
|
||||
{ on = "<Down>", run = "arrow next", desc = "Next line" },
|
||||
|
||||
# Filtering
|
||||
{ on = "f", run = "filter", desc = "Filter help items" },
|
||||
{ on = "<C-p>", run = "arrow prev", desc = "Previous line" },
|
||||
{ on = "<C-n>", run = "arrow next", desc = "Next line" },
|
||||
]
|
||||
|
|
|
|||
|
|
@ -226,11 +226,10 @@ hovered = { fg = "magenta", bold = true }
|
|||
# : Help menu {{{
|
||||
|
||||
[help]
|
||||
on = { fg = "cyan" }
|
||||
run = { fg = "magenta" }
|
||||
desc = {}
|
||||
border = { fg = "blue" }
|
||||
chord = { fg = "cyan" }
|
||||
action = {}
|
||||
hovered = { reversed = true, bold = true }
|
||||
footer = { fg = "black", bg = "white" }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
|
|
|||
|
|
@ -226,11 +226,10 @@ hovered = { fg = "magenta", bold = true }
|
|||
# : Help menu {{{
|
||||
|
||||
[help]
|
||||
on = { fg = "cyan" }
|
||||
run = { fg = "magenta" }
|
||||
desc = {}
|
||||
border = { fg = "blue" }
|
||||
chord = { fg = "cyan" }
|
||||
action = {}
|
||||
hovered = { reversed = true, bold = true }
|
||||
footer = { fg = "black", bg = "white" }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use serde::{Deserialize, Deserializer, de};
|
|||
use serde_with::{DeserializeAs, DisplayFromStr, OneOrMany};
|
||||
use yazi_binding::Iter;
|
||||
use yazi_codegen::DeserializeOver2;
|
||||
use yazi_shared::{Layer, event::{Actions, deserialize_actions}, id::Id};
|
||||
use yazi_shared::{event::{Actions, deserialize_actions}, id::Id};
|
||||
|
||||
use super::{Key, ids::chord_id};
|
||||
use crate::{Mixable, Platform, keymap::{ChordArc, Chords}};
|
||||
|
|
@ -14,12 +14,12 @@ use crate::{Mixable, Platform, keymap::{ChordArc, Chords}};
|
|||
static RE: OnceLock<Regex> = OnceLock::new();
|
||||
|
||||
#[derive(Debug, Default, Deserialize, DeserializeOver2)]
|
||||
pub struct Chord<const L: u8 = { Layer::Null as u8 }> {
|
||||
pub struct Chord {
|
||||
#[serde(skip, default = "chord_id")]
|
||||
pub id: Id,
|
||||
#[serde(deserialize_with = "deserialize_on")]
|
||||
pub on: Vec<Key>,
|
||||
#[serde(deserialize_with = "deserialize_actions::<L, _>")]
|
||||
#[serde(deserialize_with = "deserialize_actions")]
|
||||
pub run: Actions,
|
||||
#[serde(default)]
|
||||
pub desc: String,
|
||||
|
|
@ -27,7 +27,7 @@ pub struct Chord<const L: u8 = { Layer::Null as u8 }> {
|
|||
pub r#for: Platform,
|
||||
}
|
||||
|
||||
impl<const L: u8> Clone for Chord<L> {
|
||||
impl Clone for Chord {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
id: chord_id(),
|
||||
|
|
@ -39,17 +39,21 @@ impl<const L: u8> Clone for Chord<L> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<const L: u8> PartialEq for Chord<L> {
|
||||
impl AsRef<Self> for Chord {
|
||||
fn as_ref(&self) -> &Self { self }
|
||||
}
|
||||
|
||||
impl PartialEq for Chord {
|
||||
fn eq(&self, other: &Self) -> bool { self.on == other.on }
|
||||
}
|
||||
|
||||
impl<const L: u8> Eq for Chord<L> {}
|
||||
impl Eq for Chord {}
|
||||
|
||||
impl<const L: u8> Hash for Chord<L> {
|
||||
impl Hash for Chord {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) { self.on.hash(state) }
|
||||
}
|
||||
|
||||
impl<const L: u8> Chord<L> {
|
||||
impl Chord {
|
||||
pub fn on(&self) -> String { self.on.iter().map(ToString::to_string).collect() }
|
||||
|
||||
pub fn run(&self) -> String {
|
||||
|
|
@ -66,20 +70,13 @@ impl<const L: u8> Chord<L> {
|
|||
|
||||
pub fn desc_or_run(&self) -> Cow<'_, str> { self.desc().unwrap_or_else(|| self.run().into()) }
|
||||
|
||||
pub fn contains(&self, s: &str) -> bool {
|
||||
let s = s.to_lowercase();
|
||||
self.desc().map(|d| d.to_lowercase().contains(&s)) == Some(true)
|
||||
|| self.run().to_lowercase().contains(&s)
|
||||
|| self.on().to_lowercase().contains(&s)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn noop(&self) -> bool {
|
||||
self.run.len() == 1 && self.run[0].name == "noop" && self.run[0].args.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const L: u8> Mixable for Chord<L> {
|
||||
impl Mixable for Chord {
|
||||
fn filter(&self) -> bool { self.r#for.matches() && !self.noop() }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,37 +1,40 @@
|
|||
use std::{ops::{Deref, DerefMut}, sync::Arc};
|
||||
|
||||
use mlua::{UserData, UserDataFields, Value};
|
||||
use mlua::{FromLua, Lua, UserData, UserDataFields, Value};
|
||||
use serde::Deserialize;
|
||||
use yazi_codegen::FromLuaOwned;
|
||||
use yazi_shared::{Layer, event::ActionCow};
|
||||
use yazi_shim::mlua::UserDataFieldsExt;
|
||||
|
||||
use crate::{Mixable, keymap::Chord};
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone, Debug, Default, Deserialize, FromLuaOwned)]
|
||||
pub struct ChordArc<const L: u8 = { Layer::Null as u8 }>(Arc<Chord<L>>);
|
||||
#[derive(Clone, Debug, Default, Deserialize)]
|
||||
pub struct ChordArc(Arc<Chord>);
|
||||
|
||||
impl<const L: u8> Deref for ChordArc<L> {
|
||||
type Target = Arc<Chord<L>>;
|
||||
impl Deref for ChordArc {
|
||||
type Target = Arc<Chord>;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
||||
impl<const L: u8> DerefMut for ChordArc<L> {
|
||||
impl DerefMut for ChordArc {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
|
||||
}
|
||||
|
||||
impl<const L: u8> From<&Self> for ChordArc<L> {
|
||||
impl AsRef<Chord> for ChordArc {
|
||||
fn as_ref(&self) -> &Chord { self }
|
||||
}
|
||||
|
||||
impl From<&Self> for ChordArc {
|
||||
fn from(value: &Self) -> Self { value.clone() }
|
||||
}
|
||||
|
||||
impl<const L: u8, const M: u8> From<Chord<L>> for ChordArc<M> {
|
||||
fn from(value: Chord<L>) -> Self { ChordArc(Arc::new(value)).into_erased() }
|
||||
impl From<Chord> for ChordArc {
|
||||
fn from(value: Chord) -> Self { Self(Arc::new(value)) }
|
||||
}
|
||||
|
||||
impl<const L: u8> From<ChordArc<L>> for Chord<L> {
|
||||
fn from(value: ChordArc<L>) -> Self {
|
||||
impl From<ChordArc> for Chord {
|
||||
fn from(value: ChordArc) -> Self {
|
||||
match Arc::try_unwrap(value.0) {
|
||||
Ok(c) => c,
|
||||
Err(arc) => Self::clone(&arc),
|
||||
|
|
@ -39,60 +42,53 @@ impl<const L: u8> From<ChordArc<L>> for Chord<L> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<const L: u8> From<&ChordArc<L>> for Chord<L> {
|
||||
fn from(value: &ChordArc<L>) -> Self { Self::clone(value) }
|
||||
impl From<&ChordArc> for Chord {
|
||||
fn from(value: &ChordArc) -> Self { Self::clone(value) }
|
||||
}
|
||||
|
||||
impl TryFrom<(Value, Layer)> for ChordArc {
|
||||
type Error = mlua::Error;
|
||||
|
||||
fn try_from((value, layer): (Value, Layer)) -> Result<Self, Self::Error> {
|
||||
use Chord as C;
|
||||
use Layer as L;
|
||||
|
||||
let de = mlua::serde::Deserializer::new(value);
|
||||
Ok(match layer {
|
||||
L::Null => C::<{ L::Null as u8 }>::deserialize(de)?.into(),
|
||||
L::App => C::<{ L::App as u8 }>::deserialize(de)?.into(),
|
||||
L::Mgr => C::<{ L::Mgr as u8 }>::deserialize(de)?.into(),
|
||||
L::Tasks => C::<{ L::Tasks as u8 }>::deserialize(de)?.into(),
|
||||
L::Spot => C::<{ L::Spot as u8 }>::deserialize(de)?.into(),
|
||||
L::Pick => C::<{ L::Pick as u8 }>::deserialize(de)?.into(),
|
||||
L::Input => C::<{ L::Input as u8 }>::deserialize(de)?.into(),
|
||||
L::Confirm => C::<{ L::Confirm as u8 }>::deserialize(de)?.into(),
|
||||
L::Help => C::<{ L::Help as u8 }>::deserialize(de)?.into(),
|
||||
L::Cmp => C::<{ L::Cmp as u8 }>::deserialize(de)?.into(),
|
||||
L::Which => C::<{ L::Which as u8 }>::deserialize(de)?.into(),
|
||||
L::Notify => C::<{ L::Notify as u8 }>::deserialize(de)?.into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<const L: u8> ChordArc<L> {
|
||||
pub fn as_erased<const M: u8>(&self) -> &ChordArc<M> {
|
||||
unsafe { &*(self as *const Self as *const ChordArc<M>) }
|
||||
impl ChordArc {
|
||||
pub fn to_seq(&self, layer: Layer) -> Vec<ActionCow> {
|
||||
self
|
||||
.run
|
||||
.iter()
|
||||
.rev()
|
||||
.cloned()
|
||||
.map(|mut a| {
|
||||
a.layer = a.layer.or(layer);
|
||||
a.into()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn into_erased<const M: u8>(self) -> ChordArc<M> {
|
||||
ChordArc(unsafe { Arc::from_raw(Arc::into_raw(self.0) as *const Chord<M>) })
|
||||
}
|
||||
|
||||
pub fn to_seq(&self) -> Vec<ActionCow> {
|
||||
self.run.iter().rev().cloned().map(Into::into).collect()
|
||||
}
|
||||
|
||||
pub fn into_seq(self) -> Vec<ActionCow> {
|
||||
pub fn into_seq(self, layer: Layer) -> Vec<ActionCow> {
|
||||
match Arc::try_unwrap(self.0) {
|
||||
Ok(c) => c.run.into_iter().rev().map(Into::into).collect(),
|
||||
Err(arc) => Self(arc).to_seq(),
|
||||
Ok(c) => c
|
||||
.run
|
||||
.into_iter()
|
||||
.rev()
|
||||
.map(|mut a| {
|
||||
a.layer = a.layer.or(layer);
|
||||
a.into()
|
||||
})
|
||||
.collect(),
|
||||
Err(arc) => Self(arc).to_seq(layer),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<const L: u8> Mixable for ChordArc<L> {
|
||||
impl Mixable for ChordArc {
|
||||
fn filter(&self) -> bool { self.0.filter() }
|
||||
}
|
||||
|
||||
impl FromLua for ChordArc {
|
||||
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
|
||||
Ok(match value {
|
||||
Value::UserData(ud) => ud.take()?,
|
||||
_ => Chord::deserialize(mlua::serde::Deserializer::new(value))?.into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for ChordArc {
|
||||
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("id", |_, me| Ok(me.id));
|
||||
|
|
|
|||
|
|
@ -1,53 +1,36 @@
|
|||
use std::{ops::Deref, sync::Arc};
|
||||
|
||||
use arc_swap::ArcSwap;
|
||||
use mlua::{ExternalError, ExternalResult, MetaMethod, Table, UserData, UserDataMethods, Value};
|
||||
use mlua::{ExternalError, ExternalResult, MetaMethod, Table, UserData, UserDataMethods};
|
||||
use serde::Deserialize;
|
||||
use yazi_shared::{Layer, event::Actions};
|
||||
use yazi_shared::event::Actions;
|
||||
use yazi_shim::{arc_swap::{ArcSwapExt, IntoPointee}, mlua::DeserializeOverLua, vec::{IndexAtError, VecExt}};
|
||||
|
||||
use crate::keymap::{Chord, ChordArc, ChordIter, ChordMatcher};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct Chords<const L: u8 = { Layer::Null as u8 }> {
|
||||
pub chords: ArcSwap<Vec<ChordArc<L>>>,
|
||||
#[serde(skip, default = "layer_default::<L>")]
|
||||
pub layer: Layer,
|
||||
pub struct Chords(ArcSwap<Vec<ChordArc>>);
|
||||
|
||||
impl Deref for Chords {
|
||||
type Target = ArcSwap<Vec<ChordArc>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
||||
impl<const L: u8> Default for Chords<L> {
|
||||
fn default() -> Self {
|
||||
Self { chords: ArcSwap::default(), layer: Layer::from_repr(L).unwrap_or_default() }
|
||||
}
|
||||
impl From<Vec<ChordArc>> for Chords {
|
||||
fn from(inner: Vec<ChordArc>) -> Self { Self(inner.into_pointee()) }
|
||||
}
|
||||
|
||||
impl<const L: u8> Deref for Chords<L> {
|
||||
type Target = ArcSwap<Vec<ChordArc<L>>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.chords }
|
||||
}
|
||||
|
||||
impl<const L: u8> From<Vec<ChordArc<L>>> for Chords<L> {
|
||||
fn from(inner: Vec<ChordArc<L>>) -> Self {
|
||||
Self { chords: inner.into_pointee(), layer: Layer::from_repr(L).unwrap_or_default() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<const L: u8> Chords<L> {
|
||||
pub fn as_erased<const M: u8>(&self) -> Arc<Vec<ChordArc<M>>> {
|
||||
let chords = self.chords.load_full();
|
||||
unsafe { Arc::from_raw(Arc::into_raw(chords) as *const Vec<ChordArc<M>>) }
|
||||
}
|
||||
|
||||
impl Chords {
|
||||
pub fn insert(&self, index: isize, rule: ChordArc) -> Result<(), IndexAtError> {
|
||||
self.chords.try_rcu(|rules| {
|
||||
self.0.try_rcu(|rules| {
|
||||
let (before, after) = rules.split_at(rules.index_at(index)?);
|
||||
Ok(
|
||||
before
|
||||
.iter()
|
||||
.cloned()
|
||||
.chain([rule.as_erased().clone()])
|
||||
.chain([rule.clone()])
|
||||
.chain(after.iter().cloned())
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
|
|
@ -57,9 +40,9 @@ impl<const L: u8> Chords<L> {
|
|||
}
|
||||
|
||||
pub fn remove(&self, matcher: ChordMatcher) {
|
||||
self.chords.rcu(|chords| {
|
||||
self.0.rcu(|chords| {
|
||||
let mut next = Vec::clone(chords);
|
||||
next.retain(|arc| !matcher.matches(arc.as_erased()));
|
||||
next.retain(|arc| !matcher.matches(arc));
|
||||
next
|
||||
});
|
||||
}
|
||||
|
|
@ -69,11 +52,11 @@ impl<const L: u8> Chords<L> {
|
|||
matcher: ChordMatcher,
|
||||
f: impl Fn(Chord) -> Result<Chord, E>,
|
||||
) -> Result<(), E> {
|
||||
self.chords.try_rcu(|rules| {
|
||||
self.0.try_rcu(|rules| {
|
||||
let mut next = Vec::clone(rules);
|
||||
for arc in &mut next {
|
||||
if matcher.matches(arc.as_erased()) {
|
||||
*arc = f(Chord::clone(arc.as_erased()))?.into();
|
||||
if matcher.matches(arc) {
|
||||
*arc = f(Chord::clone(arc))?.into();
|
||||
}
|
||||
}
|
||||
Ok(Arc::new(next))
|
||||
|
|
@ -82,8 +65,8 @@ impl<const L: u8> Chords<L> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn unwrap_unchecked(self) -> Vec<ChordArc<L>> {
|
||||
Arc::try_unwrap(self.chords.into_inner()).expect("unique chords arc")
|
||||
pub(crate) fn unwrap_unchecked(self) -> Vec<ChordArc> {
|
||||
Arc::try_unwrap(self.0.into_inner()).expect("unique chords arc")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -91,21 +74,19 @@ impl UserData for &Chords {
|
|||
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_method("match", |_, &me, matcher: Option<ChordMatcher>| {
|
||||
Ok(match matcher {
|
||||
Some(matcher) => ChordIter { chords: me.as_erased(), matcher, ..Default::default() },
|
||||
Some(matcher) => ChordIter { chords: me.load_full(), matcher, ..Default::default() },
|
||||
None => me.into(),
|
||||
})
|
||||
});
|
||||
|
||||
methods.add_method("insert", |_, me, (index, value): (isize, Value)| {
|
||||
methods.add_method("insert", |_, me, (index, chord): (isize, ChordArc)| {
|
||||
let index = match index {
|
||||
1.. => index - 1,
|
||||
0 => return Err("index must be 1-based or negative".into_lua_err()),
|
||||
_ => index,
|
||||
};
|
||||
|
||||
let chord = ChordArc::try_from((value, me.layer))?;
|
||||
me.insert(index, chord.clone()).into_lua_err()?;
|
||||
|
||||
Ok(chord)
|
||||
});
|
||||
|
||||
|
|
@ -118,7 +99,7 @@ impl UserData for &Chords {
|
|||
let mut run: Option<Actions> = table.raw_get("run")?;
|
||||
if let Some(run) = &mut run {
|
||||
table.raw_remove("run")?;
|
||||
run.set(me.layer, yazi_shared::Source::Key);
|
||||
run.set_source(yazi_shared::Source::Key);
|
||||
}
|
||||
|
||||
me.update(matcher, |mut chord| {
|
||||
|
|
@ -133,5 +114,3 @@ impl UserData for &Chords {
|
|||
methods.add_meta_method(MetaMethod::Len, |_, me, ()| Ok(me.load().len()));
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn layer_default<const L: u8>() -> Layer { Layer::from_repr(L).unwrap_or_default() }
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::{ops::Deref, sync::Arc};
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use serde::Deserialize;
|
||||
|
|
@ -11,20 +11,20 @@ use crate::keymap::ChordArc;
|
|||
|
||||
#[derive(Deserialize, DeserializeOver, DeserializeOver1)]
|
||||
pub struct Keymap {
|
||||
pub mgr: KeymapSection<{ Layer::Mgr as u8 }>,
|
||||
pub tasks: KeymapSection<{ Layer::Tasks as u8 }>,
|
||||
pub spot: KeymapSection<{ Layer::Spot as u8 }>,
|
||||
pub pick: KeymapSection<{ Layer::Pick as u8 }>,
|
||||
pub input: KeymapSection<{ Layer::Input as u8 }>,
|
||||
pub confirm: KeymapSection<{ Layer::Confirm as u8 }>,
|
||||
pub help: KeymapSection<{ Layer::Help as u8 }>,
|
||||
pub cmp: KeymapSection<{ Layer::Cmp as u8 }>,
|
||||
pub mgr: KeymapSection,
|
||||
pub tasks: KeymapSection,
|
||||
pub spot: KeymapSection,
|
||||
pub pick: KeymapSection,
|
||||
pub input: KeymapSection,
|
||||
pub confirm: KeymapSection,
|
||||
pub help: KeymapSection,
|
||||
pub cmp: KeymapSection,
|
||||
}
|
||||
|
||||
impl Keymap {
|
||||
pub fn chords(&self, layer: Layer) -> Arc<Vec<ChordArc>> {
|
||||
match self.section(layer) {
|
||||
Some(s) => s.deref().as_erased(),
|
||||
Some(s) => s.load_full(),
|
||||
None => Arc::new(Vec::new()),
|
||||
}
|
||||
}
|
||||
|
|
@ -34,14 +34,14 @@ impl Keymap {
|
|||
|
||||
Some(match layer {
|
||||
L::Null | L::App => None?,
|
||||
L::Mgr => self.mgr.as_erased(),
|
||||
L::Tasks => self.tasks.as_erased(),
|
||||
L::Spot => self.spot.as_erased(),
|
||||
L::Pick => self.pick.as_erased(),
|
||||
L::Input => self.input.as_erased(),
|
||||
L::Confirm => self.confirm.as_erased(),
|
||||
L::Help => self.help.as_erased(),
|
||||
L::Cmp => self.cmp.as_erased(),
|
||||
L::Mgr => &self.mgr,
|
||||
L::Tasks => &self.tasks,
|
||||
L::Spot => &self.spot,
|
||||
L::Pick => &self.pick,
|
||||
L::Input => &self.input,
|
||||
L::Confirm => &self.confirm,
|
||||
L::Help => &self.help,
|
||||
L::Cmp => &self.cmp,
|
||||
L::Which => None?,
|
||||
L::Notify => None?,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -4,53 +4,45 @@ use hashbrown::HashSet;
|
|||
use mlua::{UserData, UserDataFields};
|
||||
use serde::Deserialize;
|
||||
use yazi_codegen::DeserializeOver2;
|
||||
use yazi_shared::Layer;
|
||||
use yazi_shim::{mlua::UserDataFieldsExt, toml::DeserializeOverHook};
|
||||
|
||||
use super::{Key, Chord, Chords, chords::layer_default};
|
||||
use super::{Chord, Chords, Key};
|
||||
use crate::{keymap::ChordArc, mix};
|
||||
|
||||
#[derive(Default, Deserialize, DeserializeOver2)]
|
||||
pub struct KeymapSection<const L: u8 = { Layer::Null as u8 }> {
|
||||
keymap: Chords<L>,
|
||||
pub struct KeymapSection {
|
||||
keymap: Chords,
|
||||
#[serde(default)]
|
||||
prepend_keymap: Vec<Chord<L>>,
|
||||
prepend_keymap: Vec<Chord>,
|
||||
#[serde(default)]
|
||||
append_keymap: Vec<Chord<L>>,
|
||||
#[serde(default = "layer_default::<L>")]
|
||||
layer: Layer,
|
||||
append_keymap: Vec<Chord>,
|
||||
}
|
||||
|
||||
impl<const L: u8> Deref for KeymapSection<L> {
|
||||
type Target = Chords<L>;
|
||||
impl Deref for KeymapSection {
|
||||
type Target = Chords;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.keymap }
|
||||
}
|
||||
|
||||
impl<const L: u8> KeymapSection<L> {
|
||||
pub fn as_erased<const M: u8>(&self) -> &KeymapSection<M> {
|
||||
unsafe { &*(self as *const Self as *const KeymapSection<M>) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<const L: u8> DeserializeOverHook for KeymapSection<L> {
|
||||
impl DeserializeOverHook for KeymapSection {
|
||||
fn deserialize_over_hook(self) -> Result<Self, toml::de::Error> {
|
||||
#[inline]
|
||||
fn on<const L: u8>(Chord { on, .. }: &Chord<L>) -> [Key; 2] {
|
||||
[on.first().copied().unwrap_or_default(), on.get(1).copied().unwrap_or_default()]
|
||||
fn on<T: AsRef<Chord>>(chord: T) -> [Key; 2] {
|
||||
let c = chord.as_ref();
|
||||
[c.on.first().copied().unwrap_or_default(), c.on.get(1).copied().unwrap_or_default()]
|
||||
}
|
||||
|
||||
let keymap = self.keymap.unwrap_unchecked();
|
||||
let a_seen: HashSet<_> = self.prepend_keymap.iter().map(on).collect();
|
||||
let b_seen: HashSet<_> = keymap.iter().map(|c| on(c)).collect();
|
||||
let b_seen: HashSet<_> = keymap.iter().map(on).collect();
|
||||
|
||||
let keymap: Vec<ChordArc<L>> = mix(
|
||||
let keymap: Vec<ChordArc> = mix(
|
||||
self.prepend_keymap,
|
||||
keymap.into_iter().filter(|v| !a_seen.contains(&on(v))),
|
||||
self.append_keymap.into_iter().filter(|v| !b_seen.contains(&on(v))),
|
||||
);
|
||||
|
||||
Ok(Self { keymap: keymap.into(), layer: self.layer, ..Default::default() })
|
||||
Ok(Self { keymap: keymap.into(), ..Default::default() })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
9
yazi-config/src/popup/help.rs
Normal file
9
yazi-config/src/popup/help.rs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
use yazi_binding::position::{Offset, Origin, Position};
|
||||
|
||||
pub struct Help;
|
||||
|
||||
impl Help {
|
||||
pub fn position() -> Position {
|
||||
Position::new(Origin::Center, Offset { x: 0, y: 0, width: 80, height: 25 })
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +1 @@
|
|||
yazi_macro::mod_flat!(confirm input options pick);
|
||||
yazi_macro::mod_flat!(confirm help input options pick);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ use yazi_fs::{Xdg, ok_or_not_found, path::sanitize_path};
|
|||
use yazi_shim::{arc_swap::IntoPointee, cell::SyncCell};
|
||||
|
||||
use super::{Custom, Filetype, Flavor, Icon};
|
||||
use crate::YAZI;
|
||||
|
||||
#[derive(Deserialize, DeserializeOver, DeserializeOver1, Overlay)]
|
||||
pub struct Theme {
|
||||
|
|
@ -232,7 +233,11 @@ pub struct Input {
|
|||
|
||||
impl From<&Input> for yazi_widgets::input::InputStyles {
|
||||
fn from(input: &Input) -> Self {
|
||||
Self { normal: Some(input.value.get().into()), selected: Some(input.selected.get().into()) }
|
||||
Self {
|
||||
normal: Some(input.value.get().into()),
|
||||
selected: Some(input.selected.get().into()),
|
||||
blink: Some(YAZI.input.cursor_blink),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -259,12 +264,10 @@ pub struct Tasks {
|
|||
// --- Help
|
||||
#[derive(Deserialize, DeserializeOver, DeserializeOver2, Overlay)]
|
||||
pub struct Help {
|
||||
pub on: SyncCell<StyleFlat>,
|
||||
pub run: SyncCell<StyleFlat>,
|
||||
pub desc: SyncCell<StyleFlat>,
|
||||
|
||||
pub border: SyncCell<StyleFlat>,
|
||||
pub chord: SyncCell<StyleFlat>,
|
||||
pub action: SyncCell<StyleFlat>,
|
||||
pub hovered: SyncCell<StyleFlat>,
|
||||
pub footer: SyncCell<StyleFlat>,
|
||||
}
|
||||
|
||||
fn deserialize_syntect_theme<'de, D>(deserializer: D) -> Result<ArcSwap<PathBuf>, D::Error>
|
||||
|
|
|
|||
|
|
@ -33,6 +33,11 @@ impl Core {
|
|||
}
|
||||
|
||||
pub fn cursor(&self) -> Option<(Position, SetCursorStyle)> {
|
||||
if let Some(cursor) = self.help.cursor() {
|
||||
let Rect { x, y, .. } = self.mgr.area(self.help.position).padding(self.help.padding());
|
||||
return Some((Position { x: x + cursor, y }, self.help.cursor_shape()?));
|
||||
}
|
||||
|
||||
if let Some(guard) = self.input.lock() {
|
||||
let Rect { x, y, .. } = match &guard {
|
||||
InputGuard::Main(_) => self.mgr.area(self.input.position()?).padding(self.input.padding()),
|
||||
|
|
@ -41,10 +46,6 @@ impl Core {
|
|||
return Some((Position { x: x + guard.cursor(), y }, guard.cursor_shape()));
|
||||
}
|
||||
|
||||
if let Some((x, y)) = self.help.cursor() {
|
||||
return Some((Position { x, y }, self.help.cursor_shape()?));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,46 +1,33 @@
|
|||
use anyhow::Result;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
use ratatui_widgets::block::Padding;
|
||||
use yazi_binding::position::Position;
|
||||
use yazi_config::{KEYMAP, keymap::ChordArc};
|
||||
use yazi_macro::{act, render, render_and};
|
||||
use yazi_macro::render;
|
||||
use yazi_shared::Layer;
|
||||
use yazi_term::{TERM, event::{KeyCode, KeyEvent}};
|
||||
use yazi_term::event::KeyEvent;
|
||||
use yazi_tty::sequence::SetCursorStyle;
|
||||
use yazi_widgets::Scrollable;
|
||||
|
||||
use crate::help::HELP_MARGIN;
|
||||
use yazi_widgets::{Scrollable, input::Input};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Help {
|
||||
pub visible: bool,
|
||||
pub layer: Layer,
|
||||
pub(super) bindings: Vec<ChordArc>,
|
||||
pub visible: bool,
|
||||
pub layer: Layer,
|
||||
pub position: Position,
|
||||
pub bindings: Vec<ChordArc>,
|
||||
|
||||
// Filter
|
||||
pub keyword: String,
|
||||
pub in_filter: Option<yazi_widgets::input::Input>,
|
||||
pub input: Input,
|
||||
pub keyword: String,
|
||||
|
||||
pub offset: usize,
|
||||
pub cursor: usize,
|
||||
pub height: u16,
|
||||
}
|
||||
|
||||
impl Help {
|
||||
pub fn r#type(&mut self, key: KeyEvent) -> Result<bool> {
|
||||
let Some(input) = &mut self.in_filter else { return Ok(false) };
|
||||
match key {
|
||||
KeyEvent { code: KeyCode::Escape, modifiers, .. } if modifiers.is_empty() => {
|
||||
self.in_filter = None;
|
||||
render!();
|
||||
}
|
||||
KeyEvent { code: KeyCode::Enter, modifiers, .. } if modifiers.is_empty() => {
|
||||
self.in_filter = None;
|
||||
return Ok(render_and!(true)); // Don't do the `filter_apply` below, since we already have the filtered results.
|
||||
}
|
||||
KeyEvent { code: KeyCode::Backspace, modifiers, .. } if modifiers.is_empty() => {
|
||||
act!(backspace, input)?;
|
||||
}
|
||||
_ => {
|
||||
input.r#type(key)?;
|
||||
}
|
||||
if !self.input.r#type(key)? {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
self.filter_apply();
|
||||
|
|
@ -48,15 +35,20 @@ impl Help {
|
|||
}
|
||||
|
||||
pub fn filter_apply(&mut self) {
|
||||
let kw = self.in_filter.as_ref().map_or("", |i| i.value());
|
||||
let kw = self.input.value();
|
||||
|
||||
if kw.is_empty() {
|
||||
self.keyword = String::new();
|
||||
self.keyword.clear();
|
||||
self.bindings = KEYMAP.chords(self.layer).iter().cloned().collect();
|
||||
} else if self.keyword != kw {
|
||||
let lowercased = kw.to_lowercase();
|
||||
self.keyword = kw.to_owned();
|
||||
self.bindings =
|
||||
KEYMAP.chords(self.layer).iter().filter(|&c| c.contains(kw)).cloned().collect();
|
||||
self.bindings = KEYMAP
|
||||
.chords(self.layer)
|
||||
.iter()
|
||||
.filter(|&c| c.desc_or_run().to_lowercase().contains(&lowercased))
|
||||
.cloned()
|
||||
.collect();
|
||||
}
|
||||
|
||||
render!(self.scroll(0));
|
||||
|
|
@ -64,15 +56,7 @@ impl Help {
|
|||
}
|
||||
|
||||
impl Help {
|
||||
// --- Keyword
|
||||
pub fn keyword(&self) -> Option<String> {
|
||||
self
|
||||
.in_filter
|
||||
.as_ref()
|
||||
.map(|i| i.value())
|
||||
.or(Some(self.keyword.as_str()).filter(|&s| !s.is_empty()))
|
||||
.map(|s| format!("Filter: {s}"))
|
||||
}
|
||||
pub fn padding(&self) -> Padding { Padding::new(1, 1, 1, 1) }
|
||||
|
||||
// --- Bindings
|
||||
pub fn window(&self) -> &[ChordArc] {
|
||||
|
|
@ -81,27 +65,22 @@ impl Help {
|
|||
}
|
||||
|
||||
// --- Cursor
|
||||
pub fn cursor(&self) -> Option<(u16, u16)> {
|
||||
if !self.visible || self.in_filter.is_none() {
|
||||
return None;
|
||||
}
|
||||
if let Some(kw) = self.keyword() {
|
||||
return Some((kw.width() as u16, TERM.dimension().rows));
|
||||
}
|
||||
None
|
||||
}
|
||||
pub fn cursor(&self) -> Option<u16> { self.visible.then_some(self.input.cursor()) }
|
||||
|
||||
pub fn rel_cursor(&self) -> usize { self.cursor - self.offset }
|
||||
|
||||
pub fn cursor_shape(&self) -> Option<SetCursorStyle> {
|
||||
Some(self.in_filter.as_ref()?.cursor_shape())
|
||||
self.visible.then_some(self.input.cursor_shape())
|
||||
}
|
||||
}
|
||||
|
||||
impl Scrollable for Help {
|
||||
fn total(&self) -> usize { self.bindings.len() }
|
||||
|
||||
fn limit(&self) -> usize { TERM.dimension().rows.saturating_sub(HELP_MARGIN) as usize }
|
||||
fn limit(&self) -> usize {
|
||||
let p = self.padding();
|
||||
self.height.saturating_sub(p.top + /* input */ 1 + /* divider */ 1 + p.bottom) as usize
|
||||
}
|
||||
|
||||
fn cursor_mut(&mut self) -> &mut usize { &mut self.cursor }
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1 @@
|
|||
yazi_macro::mod_flat!(help);
|
||||
|
||||
const HELP_MARGIN: u16 = 1;
|
||||
|
|
|
|||
|
|
@ -7,9 +7,10 @@ use yazi_shared::{Layer, event::ActionCow};
|
|||
#[derive(Clone, Debug)]
|
||||
pub struct WhichOpt {
|
||||
pub tx: Option<mpsc::UnboundedSender<Option<ChordArc>>>,
|
||||
pub layer: Layer,
|
||||
pub cands: Vec<ChordArc>,
|
||||
pub silent: bool,
|
||||
pub times: usize,
|
||||
pub silent: bool,
|
||||
}
|
||||
|
||||
impl_data_any!(WhichOpt);
|
||||
|
|
@ -24,19 +25,21 @@ impl TryFrom<ActionCow> for WhichOpt {
|
|||
|
||||
Ok(Self {
|
||||
tx: a.take_any2("tx").transpose()?,
|
||||
layer: a.str("layer").parse()?,
|
||||
cands: a.take_any_iter().collect(),
|
||||
silent: a.bool("silent"),
|
||||
times: a.get("times").unwrap_or(0),
|
||||
silent: a.bool("silent"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(Layer, Key)> for WhichOpt {
|
||||
fn from((layer, key): (Layer, Key)) -> Self {
|
||||
impl WhichOpt {
|
||||
pub fn new(src: Layer, dist: Layer, key: Key) -> Self {
|
||||
Self {
|
||||
tx: None,
|
||||
layer: dist,
|
||||
cands: KEYMAP
|
||||
.chords(layer)
|
||||
.chords(src)
|
||||
.iter()
|
||||
.filter(|&c| c.on.len() > 1 && c.on[0] == key)
|
||||
.cloned()
|
||||
|
|
@ -55,6 +58,7 @@ impl FromLua for WhichOpt {
|
|||
|
||||
Ok(Self {
|
||||
tx: t.raw_get::<yazi_binding::MpscUnboundedTx<_>>("tx").ok().map(|t| t.0),
|
||||
layer: t.raw_get("layer")?,
|
||||
cands: t.raw_get::<Table>("cands")?.sequence_values().collect::<mlua::Result<Vec<_>>>()?,
|
||||
times: t.raw_get("times").unwrap_or_default(),
|
||||
silent: t.raw_get("silent")?,
|
||||
|
|
@ -68,6 +72,7 @@ impl IntoLua for WhichOpt {
|
|||
lua
|
||||
.create_table_from([
|
||||
("tx", self.tx.map(yazi_binding::MpscUnboundedTx).into_lua(lua)?),
|
||||
("layer", self.layer.into_lua(lua)?),
|
||||
("cands", lua.create_sequence_from(self.cands)?.into_lua(lua)?),
|
||||
("times", self.times.into_lua(lua)?),
|
||||
("silent", self.silent.into_lua(lua)?),
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
use tokio::sync::mpsc;
|
||||
use yazi_config::keymap::{ChordArc, Key};
|
||||
use yazi_macro::{emit, render_and};
|
||||
use yazi_shared::Layer;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Which {
|
||||
pub tx: Option<mpsc::UnboundedSender<Option<ChordArc>>>,
|
||||
pub times: usize,
|
||||
pub layer: Layer,
|
||||
pub cands: Vec<ChordArc>,
|
||||
pub times: usize,
|
||||
|
||||
// Active state
|
||||
pub active: bool,
|
||||
|
|
@ -32,8 +34,8 @@ impl Which {
|
|||
}
|
||||
|
||||
pub fn dismiss(&mut self, chord: Option<ChordArc>) {
|
||||
self.times = 0;
|
||||
self.cands.clear();
|
||||
self.times = 0;
|
||||
|
||||
self.active = false;
|
||||
self.silent = false;
|
||||
|
|
@ -42,7 +44,7 @@ impl Which {
|
|||
_ = tx.send(chord.as_ref().map(Into::into));
|
||||
}
|
||||
if let Some(chord) = chord {
|
||||
emit!(Seq(chord.into_seq()));
|
||||
emit!(Seq(chord.into_seq(self.layer)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -321,16 +321,18 @@ impl<'a> Executor<'a> {
|
|||
|
||||
on!(escape);
|
||||
on!(arrow);
|
||||
on!(filter);
|
||||
on!(close);
|
||||
|
||||
match action.name.as_ref() {
|
||||
// Help
|
||||
"close" => act!(help:toggle, cx, Layer::Help),
|
||||
// Plugin
|
||||
"plugin" => act!(app:plugin, cx, action),
|
||||
// Lua
|
||||
"lua" => act!(app:lua, cx, action),
|
||||
_ => succ!(),
|
||||
_ => {
|
||||
cx.help.input.execute(action)?;
|
||||
cx.help.filter_apply();
|
||||
succ!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,26 +18,18 @@ impl Widget for Bindings<'_> {
|
|||
return;
|
||||
}
|
||||
|
||||
// On
|
||||
// Chord
|
||||
let col1: Vec<_> =
|
||||
bindings.iter().map(|c| ListItem::new(c.on()).style(THEME.help.on.get())).collect();
|
||||
bindings.iter().map(|c| ListItem::new(c.on()).style(THEME.help.chord.get())).collect();
|
||||
|
||||
// Run
|
||||
let col2: Vec<_> =
|
||||
bindings.iter().map(|c| ListItem::new(c.run()).style(THEME.help.run.get())).collect();
|
||||
|
||||
// Desc
|
||||
let col3: Vec<_> = bindings
|
||||
// Action
|
||||
let col2: Vec<_> = bindings
|
||||
.iter()
|
||||
.map(|c| ListItem::new(c.desc().unwrap_or("-".into())).style(THEME.help.desc.get()))
|
||||
.map(|c| ListItem::new(c.desc_or_run()).style(THEME.help.action.get()))
|
||||
.collect();
|
||||
|
||||
let chunks = layout::Layout::horizontal([
|
||||
Constraint::Ratio(2, 10),
|
||||
Constraint::Ratio(3, 10),
|
||||
Constraint::Ratio(5, 10),
|
||||
])
|
||||
.split(area);
|
||||
let chunks =
|
||||
layout::Layout::horizontal([Constraint::Length(20), Constraint::Fill(1)]).split(area);
|
||||
|
||||
let cursor = self.core.help.rel_cursor() as u16;
|
||||
buf.set_style(
|
||||
|
|
@ -47,6 +39,5 @@ impl Widget for Bindings<'_> {
|
|||
|
||||
List::new(col1).render(chunks[0], buf);
|
||||
List::new(col2).render(chunks[1], buf);
|
||||
List::new(col3).render(chunks[2], buf);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
use ratatui_core::{buffer::Buffer, layout::{self, Constraint, Rect}, text::Line, widgets::Widget};
|
||||
use yazi_config::{KEYMAP, THEME};
|
||||
use ratatui_core::{buffer::Buffer, layout::{Alignment, Constraint, Layout, Rect}, symbols::merge::MergeStrategy, widgets::Widget};
|
||||
use ratatui_widgets::{block::{Block, Padding}, borders::BorderType};
|
||||
use yazi_config::THEME;
|
||||
use yazi_core::Core;
|
||||
use yazi_shim::ratatui::Padable;
|
||||
|
||||
use super::Bindings;
|
||||
|
||||
|
|
@ -10,27 +12,39 @@ pub(crate) struct Help<'a> {
|
|||
|
||||
impl<'a> Help<'a> {
|
||||
pub fn new(core: &'a Core) -> Self { Self { core } }
|
||||
|
||||
fn tips() -> String {
|
||||
match KEYMAP.help.load().iter().find(|&c| c.run.iter().any(|a| a.name == "filter")) {
|
||||
Some(c) => format!(" (Press `{}` to filter)", c.on()),
|
||||
None => String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for Help<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
fn render(self, _: Rect, buf: &mut Buffer) {
|
||||
let help = &self.core.help;
|
||||
let area = self.core.mgr.area(help.position);
|
||||
let padding = help.padding();
|
||||
|
||||
yazi_widgets::clear::Clear::default().render(area, buf);
|
||||
|
||||
let chunks = layout::Layout::vertical([Constraint::Fill(1), Constraint::Length(1)]).split(area);
|
||||
Line::styled(
|
||||
help.keyword().unwrap_or_else(|| format!("{}.help{}", help.layer, Self::tips())),
|
||||
THEME.help.footer.get(),
|
||||
)
|
||||
.render(chunks[1], buf);
|
||||
Block::bordered()
|
||||
.title(format!("{}.help", help.layer))
|
||||
.title_alignment(Alignment::Center)
|
||||
.border_type(BorderType::Rounded)
|
||||
.border_style(THEME.help.border.get())
|
||||
.render(area, buf);
|
||||
|
||||
Bindings::new(self.core).render(chunks[0], buf);
|
||||
let chunks =
|
||||
Layout::vertical([Constraint::Length(1), Constraint::Length(1), Constraint::Fill(1)])
|
||||
.split(area.padding(Padding { left: 0, right: 0, ..padding }));
|
||||
|
||||
// Input
|
||||
help.input.render(chunks[0].padding(Padding { top: 0, bottom: 0, ..padding }), buf);
|
||||
|
||||
// Divider
|
||||
Block::bordered()
|
||||
.border_type(BorderType::Rounded)
|
||||
.border_style(THEME.help.border.get())
|
||||
.merge_borders(MergeStrategy::Fuzzy)
|
||||
.render(chunks[1], buf);
|
||||
|
||||
// Bindings
|
||||
Bindings::new(self.core)
|
||||
.render(chunks[2].padding(Padding { top: 0, bottom: 0, ..padding }), buf);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use anyhow::Result;
|
||||
use yazi_actor::Ctx;
|
||||
use yazi_config::{KEYMAP, keymap::{Chord, Key}};
|
||||
use yazi_core::which::WhichOpt;
|
||||
use yazi_macro::act;
|
||||
use yazi_shared::Layer;
|
||||
use yazi_term::event::KeyEvent;
|
||||
|
|
@ -32,16 +33,17 @@ impl<'a> Router<'a> {
|
|||
let key = Key::from(key);
|
||||
Ok(match layer {
|
||||
L::Null | L::App | L::Notify => unreachable!(),
|
||||
L::Mgr | L::Tasks | L::Spot | L::Pick | L::Input | L::Confirm | L::Help => {
|
||||
self.matches(layer, key)
|
||||
L::Mgr | L::Tasks | L::Spot | L::Pick | L::Input | L::Confirm => {
|
||||
self.matches(layer, layer, key)
|
||||
}
|
||||
L::Cmp => self.matches(L::Cmp, key) || self.matches(L::Input, key),
|
||||
L::Help => self.matches(L::Help, L::Help, key) || self.matches(L::Input, L::Help, key),
|
||||
L::Cmp => self.matches(L::Cmp, L::Cmp, key) || self.matches(L::Input, L::Input, key),
|
||||
L::Which => core.which.r#type(key),
|
||||
})
|
||||
}
|
||||
|
||||
fn matches(&mut self, layer: Layer, key: Key) -> bool {
|
||||
for chord in &*KEYMAP.chords(layer) {
|
||||
fn matches(&mut self, src: Layer, dist: Layer, key: Key) -> bool {
|
||||
for chord in &*KEYMAP.chords(src) {
|
||||
let Chord { on, .. } = chord.as_ref();
|
||||
if on.is_empty() || on[0] != key {
|
||||
continue;
|
||||
|
|
@ -49,9 +51,9 @@ impl<'a> Router<'a> {
|
|||
|
||||
if on.len() > 1 {
|
||||
let cx = &mut Ctx::active(&mut self.app.core, &mut self.app.term);
|
||||
act!(which:activate, cx, (layer, key)).ok();
|
||||
act!(which:activate, cx, WhichOpt::new(src, dist, key)).ok();
|
||||
} else {
|
||||
Dispatcher::new(self.app).dispatch_seq(chord.to_seq());
|
||||
Dispatcher::new(self.app).dispatch_seq(chord.to_seq(dist));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
25
yazi-parser/src/help/close.rs
Normal file
25
yazi-parser/src/help/close.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
use mlua::{FromLua, IntoLua, Lua, LuaSerdeExt, Value};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use yazi_shared::event::ActionCow;
|
||||
use yazi_shim::mlua::SER_OPT;
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||
pub struct CloseForm {
|
||||
pub submit: bool,
|
||||
}
|
||||
|
||||
impl From<ActionCow> for CloseForm {
|
||||
fn from(a: ActionCow) -> Self { Self { submit: a.bool("submit") } }
|
||||
}
|
||||
|
||||
impl From<bool> for CloseForm {
|
||||
fn from(submit: bool) -> Self { Self { submit } }
|
||||
}
|
||||
|
||||
impl FromLua for CloseForm {
|
||||
fn from_lua(value: Value, lua: &Lua) -> mlua::Result<Self> { lua.from_value(value) }
|
||||
}
|
||||
|
||||
impl IntoLua for CloseForm {
|
||||
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> { lua.to_value_with(&self, SER_OPT) }
|
||||
}
|
||||
|
|
@ -1 +1 @@
|
|||
yazi_macro::mod_flat!(toggle);
|
||||
yazi_macro::mod_flat!(close toggle);
|
||||
|
|
|
|||
|
|
@ -110,8 +110,8 @@ pub enum Spark<'a> {
|
|||
|
||||
// Help
|
||||
HelpArrow(crate::ArrowForm),
|
||||
HelpClose(crate::help::CloseForm),
|
||||
HelpEscape(crate::VoidForm),
|
||||
HelpFilter(crate::VoidForm),
|
||||
HelpToggle(crate::help::ToggleForm),
|
||||
|
||||
// Input
|
||||
|
|
@ -303,8 +303,8 @@ impl<'a> IntoLua for Spark<'a> {
|
|||
|
||||
// Help
|
||||
Self::HelpArrow(b) => b.into_lua(lua),
|
||||
Self::HelpClose(b) => b.into_lua(lua),
|
||||
Self::HelpEscape(b) => b.into_lua(lua),
|
||||
Self::HelpFilter(b) => b.into_lua(lua),
|
||||
Self::HelpToggle(b) => b.into_lua(lua),
|
||||
|
||||
// Input
|
||||
|
|
@ -395,6 +395,7 @@ try_from_spark!(crate::cmp::ShowForm, cmp:show);
|
|||
try_from_spark!(crate::cmp::TriggerForm, cmp:trigger);
|
||||
try_from_spark!(crate::confirm::CloseForm, confirm:close);
|
||||
try_from_spark!(crate::confirm::ShowForm, confirm:show);
|
||||
try_from_spark!(crate::help::CloseForm, help:close);
|
||||
try_from_spark!(crate::help::ToggleForm, help:toggle);
|
||||
try_from_spark!(crate::input::CloseForm, input:close);
|
||||
try_from_spark!(crate::mgr::BulkExitForm, mgr:bulk_exit);
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
use mlua::{FromLua, IntoLua, Lua, Value};
|
||||
use yazi_config::keymap::Key;
|
||||
use yazi_core::which::WhichOpt;
|
||||
use yazi_shared::{Layer, event::ActionCow};
|
||||
use yazi_shared::event::ActionCow;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ActivateForm {
|
||||
pub opt: WhichOpt,
|
||||
}
|
||||
|
||||
impl From<(Layer, Key)> for ActivateForm {
|
||||
fn from(value: (Layer, Key)) -> Self { Self { opt: value.into() } }
|
||||
impl From<WhichOpt> for ActivateForm {
|
||||
fn from(opt: WhichOpt) -> Self { Self { opt } }
|
||||
}
|
||||
|
||||
impl TryFrom<ActionCow> for ActivateForm {
|
||||
|
|
|
|||
|
|
@ -361,12 +361,10 @@ fn help() -> Composer<ComposerGet, ComposerSet> {
|
|||
fn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {
|
||||
let t = &THEME.help;
|
||||
match key {
|
||||
b"on" => Style::from(&t.on).into_lua(lua),
|
||||
b"run" => Style::from(&t.run).into_lua(lua),
|
||||
b"desc" => Style::from(&t.desc).into_lua(lua),
|
||||
|
||||
b"border" => Style::from(&t.border).into_lua(lua),
|
||||
b"chord" => Style::from(&t.chord).into_lua(lua),
|
||||
b"action" => Style::from(&t.action).into_lua(lua),
|
||||
b"hovered" => Style::from(&t.hovered).into_lua(lua),
|
||||
b"footer" => Style::from(&t.footer).into_lua(lua),
|
||||
|
||||
_ => Ok(Value::Nil),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ impl Utils {
|
|||
.enumerate()
|
||||
.map(|(i, cand)| {
|
||||
let cand = cand?;
|
||||
Ok(ChordArc::from(Chord::<{ Layer::Null as u8 }> {
|
||||
Ok(ChordArc::from(Chord {
|
||||
id: yazi_config::keymap::chord_id(),
|
||||
on: Self::parse_keys(cand.raw_get("on")?)?,
|
||||
run: relay!(which:callback, [i + 1]).into(),
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ use tokio::sync::mpsc;
|
|||
use yazi_config::keymap::ChordArc;
|
||||
use yazi_core::which::WhichOpt;
|
||||
use yazi_macro::{emit, relay};
|
||||
use yazi_shared::Layer;
|
||||
|
||||
pub struct WhichProxy;
|
||||
|
||||
|
|
@ -10,9 +11,10 @@ impl WhichProxy {
|
|||
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||
emit!(Call(relay!(which:activate).with_any("opt", WhichOpt {
|
||||
tx: Some(tx),
|
||||
layer: Layer::Null,
|
||||
cands,
|
||||
silent,
|
||||
times: 0,
|
||||
silent,
|
||||
})));
|
||||
rx.recv().await?
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
use std::{ops::{Deref, DerefMut}, slice, vec};
|
||||
|
||||
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
|
||||
use serde::{Deserializer, de};
|
||||
use serde::Deserializer;
|
||||
use serde_with::{DeserializeAs, DisplayFromStr, OneOrMany};
|
||||
|
||||
use crate::{Layer, Source, event::Action};
|
||||
use crate::{Source, event::Action};
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Actions(pub Vec<Action>);
|
||||
|
|
@ -28,12 +28,9 @@ impl From<Vec<Action>> for Actions {
|
|||
}
|
||||
|
||||
impl Actions {
|
||||
pub fn set(&mut self, layer: Layer, source: Source) {
|
||||
pub fn set_source(&mut self, source: Source) {
|
||||
for action in &mut self.0 {
|
||||
action.source = source;
|
||||
if action.layer == Layer::Null {
|
||||
action.layer = layer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -68,16 +65,12 @@ impl IntoLua for Actions {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn deserialize_actions<'de, const L: u8, D>(deserializer: D) -> Result<Actions, D::Error>
|
||||
pub fn deserialize_actions<'de, D>(deserializer: D) -> Result<Actions, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let Some(layer) = Layer::from_repr(L) else {
|
||||
return Err(de::Error::custom(format!("invalid keymap layer const: {L}")));
|
||||
};
|
||||
|
||||
let mut actions = Actions(OneOrMany::<DisplayFromStr>::deserialize_as(deserializer)?);
|
||||
actions.set(layer, Source::Key);
|
||||
actions.set_source(Source::Key);
|
||||
|
||||
Ok(actions)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use mlua::{MetaMethod, UserData, UserDataMethods};
|
||||
use mlua::{ExternalError, ExternalResult, FromLua, MetaMethod, UserData, UserDataMethods, Value};
|
||||
use serde::Deserialize;
|
||||
use strum::{Display, EnumString, FromRepr, IntoStaticStr};
|
||||
use yazi_shim::strum::IntoStr;
|
||||
|
|
@ -17,8 +17,8 @@ use yazi_shim::strum::IntoStr;
|
|||
IntoStaticStr,
|
||||
PartialEq,
|
||||
)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[strum(serialize_all = "kebab-case")]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[strum(serialize_all = "lowercase")]
|
||||
#[repr(u8)]
|
||||
pub enum Layer {
|
||||
#[default]
|
||||
|
|
@ -36,6 +36,20 @@ pub enum Layer {
|
|||
Notify,
|
||||
}
|
||||
|
||||
impl Layer {
|
||||
pub fn or(self, other: Self) -> Self { if self == Self::Null { other } else { self } }
|
||||
}
|
||||
|
||||
impl FromLua for Layer {
|
||||
fn from_lua(value: Value, _: &mlua::Lua) -> mlua::Result<Self> {
|
||||
Ok(match value {
|
||||
Value::String(s) => s.to_str()?.parse().into_lua_err()?,
|
||||
Value::UserData(ud) => *ud.borrow::<Self>()?,
|
||||
_ => Err("expected a string or a Layer".into_lua_err())?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for Layer {
|
||||
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_meta_method(MetaMethod::ToString, |_, me, ()| Ok(me.into_str()));
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ use strum::IntoStaticStr;
|
|||
use yazi_shared::id::Id;
|
||||
|
||||
#[derive(Debug, IntoStaticStr)]
|
||||
#[strum(serialize_all = "lowercase")]
|
||||
pub enum InputEvent {
|
||||
Submit(String),
|
||||
Cancel(String),
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ pub struct Input {
|
|||
pub snaps: InputSnaps,
|
||||
pub styles: InputStyles,
|
||||
pub obscure: bool,
|
||||
pub blinking: bool,
|
||||
pub realtime: bool,
|
||||
pub completion: bool,
|
||||
|
||||
|
|
@ -30,7 +29,6 @@ impl Input {
|
|||
snaps: InputSnaps::new(opt.value, opt.obscure),
|
||||
styles: opt.styles,
|
||||
obscure: opt.obscure,
|
||||
blinking: opt.blinking,
|
||||
realtime: opt.realtime,
|
||||
completion: opt.completion,
|
||||
|
||||
|
|
@ -133,13 +131,14 @@ impl Input {
|
|||
pub fn cursor_shape(&self) -> SetCursorStyle {
|
||||
use InputMode as M;
|
||||
|
||||
let blink = self.styles.blink.unwrap_or(false);
|
||||
match self.mode() {
|
||||
M::Normal if self.blinking => SetCursorStyle::BlinkingBlock,
|
||||
M::Normal if !self.blinking => SetCursorStyle::SteadyBlock,
|
||||
M::Insert if self.blinking => SetCursorStyle::BlinkingBar,
|
||||
M::Insert if !self.blinking => SetCursorStyle::SteadyBar,
|
||||
M::Replace if self.blinking => SetCursorStyle::BlinkingUnderline,
|
||||
M::Replace if !self.blinking => SetCursorStyle::SteadyUnderline,
|
||||
M::Normal if blink => SetCursorStyle::BlinkingBlock,
|
||||
M::Normal if !blink => SetCursorStyle::SteadyBlock,
|
||||
M::Insert if blink => SetCursorStyle::BlinkingBar,
|
||||
M::Insert if !blink => SetCursorStyle::SteadyBar,
|
||||
M::Replace if blink => SetCursorStyle::BlinkingUnderline,
|
||||
M::Replace if !blink => SetCursorStyle::SteadyUnderline,
|
||||
M::Normal | M::Insert | M::Replace => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ impl InputArc {
|
|||
let new = lua.create_function(move |_, (_, mut opt): (Table, InputOpt)| {
|
||||
opt.styles.normal = opt.styles.normal.or(styles.normal);
|
||||
opt.styles.selected = opt.styles.selected.or(styles.selected);
|
||||
opt.styles.blink = opt.styles.blink.or(styles.blink);
|
||||
opt = opt.with_cb(|e| err!(Pubsub::pub_after_input((&e).into_str(), e.value())));
|
||||
Ok(Self::from(Input::new(opt)?))
|
||||
})?;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ use ratatui_core::style::Style;
|
|||
pub struct InputStyles {
|
||||
pub normal: Option<Style>,
|
||||
pub selected: Option<Style>,
|
||||
pub blink: Option<bool>,
|
||||
}
|
||||
|
||||
impl TryFrom<Table> for InputStyles {
|
||||
|
|
@ -14,6 +15,7 @@ impl TryFrom<Table> for InputStyles {
|
|||
Ok(Self {
|
||||
normal: t.raw_get::<Option<yazi_binding::style::Style>>("normal")?.map(Into::into),
|
||||
selected: t.raw_get::<Option<yazi_binding::style::Style>>("selected")?.map(Into::into),
|
||||
blink: t.raw_get::<Option<bool>>("blink")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue