feat: make UI extensions easier (#1257)

This commit is contained in:
三咲雅 · Misaki Masa 2024-07-12 18:32:01 +08:00 committed by sxyazi
parent 54eb0cc663
commit f5598ebb8a
No known key found for this signature in database
66 changed files with 1026 additions and 789 deletions

189
Cargo.lock generated
View file

@ -206,9 +206,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.5.0"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
name = "block-buffer"
@ -303,9 +303,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.7"
version = "4.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f"
checksum = "64acc1846d54c1fe936a78dc189c34e28d3f5afc348403f28ecf53660b9b8462"
dependencies = [
"clap_builder",
"clap_derive",
@ -313,9 +313,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.7"
version = "4.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f"
checksum = "6fb8393d67ba2e7bfaf28a23458e4e2b543cc73a99595511eb207fdb8aede942"
dependencies = [
"anstream",
"anstyle",
@ -325,9 +325,9 @@ dependencies = [
[[package]]
name = "clap_complete"
version = "4.5.6"
version = "4.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbca90c87c2a04da41e95d1856e8bcd22f159bdbfa147314d2ce5218057b0e58"
checksum = "5b4be9c4c4b1f30b78d8a750e0822b6a6102d97e62061c583a6c1dea2dfb33ae"
dependencies = [
"clap",
]
@ -354,14 +354,14 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.5.5"
version = "4.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6"
checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.67",
"syn 2.0.68",
]
[[package]]
@ -372,9 +372,9 @@ checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70"
[[package]]
name = "clipboard-win"
version = "5.3.1"
version = "5.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79f4473f5144e20d9aceaf2972478f06ddf687831eafeeb434fbaf0acc4144ad"
checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892"
dependencies = [
"error-code",
]
@ -480,7 +480,7 @@ version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
dependencies = [
"bitflags 2.5.0",
"bitflags 2.6.0",
"crossterm_winapi",
"futures-core",
"libc",
@ -537,7 +537,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim",
"syn 2.0.67",
"syn 2.0.68",
]
[[package]]
@ -548,7 +548,7 @@ checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178"
dependencies = [
"darling_core",
"quote",
"syn 2.0.67",
"syn 2.0.68",
]
[[package]]
@ -560,6 +560,37 @@ dependencies = [
"powerfmt",
]
[[package]]
name = "derive_builder"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0350b5cb0331628a5916d6c5c0b72e97393b8b6b03b47a9284f4e7f5a405ffd7"
dependencies = [
"derive_builder_macro",
]
[[package]]
name = "derive_builder_core"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d48cda787f839151732d396ac69e3473923d54312c070ee21e9effcaa8ca0b1d"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.68",
]
[[package]]
name = "derive_builder_macro"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "206868b8242f27cecce124c19fd88157fbd0dd334df2587f36417bafbc85097b"
dependencies = [
"derive_builder_core",
"syn 2.0.68",
]
[[package]]
name = "digest"
version = "0.10.7"
@ -792,7 +823,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.67",
"syn 2.0.68",
]
[[package]]
@ -846,6 +877,18 @@ dependencies = [
"wasi",
]
[[package]]
name = "getset"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e45727250e75cc04ff2846a66397da8ef2b3db8e40e0cef4df67950a07621eb9"
dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "gif"
version = "0.13.1"
@ -1110,7 +1153,7 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
dependencies = [
"bitflags 2.5.0",
"bitflags 2.6.0",
"libc",
]
@ -1257,7 +1300,7 @@ dependencies = [
"proc-macro2",
"quote",
"regex",
"syn 2.0.67",
"syn 2.0.68",
]
[[package]]
@ -1282,7 +1325,7 @@ version = "6.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
dependencies = [
"bitflags 2.5.0",
"bitflags 2.6.0",
"filetime",
"fsevent-sys",
"inotify",
@ -1366,7 +1409,7 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8"
dependencies = [
"bitflags 2.5.0",
"bitflags 2.6.0",
"block2",
"libc",
"objc2",
@ -1588,7 +1631,7 @@ version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d16546c5b5962abf8ce6e2881e722b4e0ae3b6f1a08a26ae3573c55853ca68d3"
dependencies = [
"bitflags 2.5.0",
"bitflags 2.6.0",
"cassowary",
"compact_str",
"crossterm",
@ -1638,7 +1681,7 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd"
dependencies = [
"bitflags 2.5.0",
"bitflags 2.6.0",
]
[[package]]
@ -1699,7 +1742,7 @@ version = "0.38.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
dependencies = [
"bitflags 2.5.0",
"bitflags 2.6.0",
"errno",
"libc",
"linux-raw-sys",
@ -1735,9 +1778,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde"
version = "1.0.203"
version = "1.0.204"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"
checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12"
dependencies = [
"serde_derive",
]
@ -1754,20 +1797,20 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.203"
version = "1.0.204"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.67",
"syn 2.0.68",
]
[[package]]
name = "serde_json"
version = "1.0.117"
version = "1.0.120"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3"
checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5"
dependencies = [
"itoa",
"ryu",
@ -1893,7 +1936,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ff9eaf853dec4c8802325d8b6d3dffa86cc707fd7a1a4cdbf416e13b061787a"
dependencies = [
"quote",
"syn 2.0.67",
"syn 2.0.68",
]
[[package]]
@ -1927,7 +1970,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.67",
"syn 2.0.68",
]
[[package]]
@ -1937,14 +1980,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.67"
version = "2.0.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff8655ed1d86f3af4ee3fd3263786bc14245ad17c4c7e85ba7187fb3ae028c90"
checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9"
dependencies = [
"proc-macro2",
"quote",
@ -1989,7 +2033,7 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.67",
"syn 2.0.68",
]
[[package]]
@ -2108,7 +2152,7 @@ checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.67",
"syn 2.0.68",
]
[[package]]
@ -2159,9 +2203,9 @@ dependencies = [
[[package]]
name = "toml_edit"
version = "0.22.14"
version = "0.22.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38"
checksum = "d59a3a72298453f564e2b111fa896f8d07fabb36f51f06d7e875fc5e0b5a3ef1"
dependencies = [
"indexmap",
"serde",
@ -2201,7 +2245,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.67",
"syn 2.0.68",
]
[[package]]
@ -2371,7 +2415,7 @@ dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn 2.0.67",
"syn 2.0.68",
]
[[package]]
@ -2382,14 +2426,41 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "vergen"
version = "8.3.1"
version = "9.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e27d6bdd219887a9eadd19e1c34f32e47fa332301184935c6d9bca26f3cca525"
checksum = "c32e7318e93a9ac53693b6caccfb05ff22e04a44c7cf8a279051f24c09da286f"
dependencies = [
"anyhow",
"cfg-if",
"derive_builder",
"rustversion",
"time",
"vergen-lib",
]
[[package]]
name = "vergen-gitcl"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3bbdc9746577cb4767f218d320ee0b623d415e8130332f8f562b910b61cc2c4e"
dependencies = [
"anyhow",
"derive_builder",
"rustversion",
"time",
"vergen",
"vergen-lib",
]
[[package]]
name = "vergen-lib"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e06bee42361e43b60f363bad49d63798d0f42fb1768091812270eca00c784720"
dependencies = [
"anyhow",
"derive_builder",
"getset",
"rustversion",
]
[[package]]
@ -2435,7 +2506,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.67",
"syn 2.0.68",
"wasm-bindgen-shared",
]
@ -2457,7 +2528,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.67",
"syn 2.0.68",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@ -2556,7 +2627,7 @@ checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.67",
"syn 2.0.68",
]
[[package]]
@ -2567,7 +2638,7 @@ checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.67",
"syn 2.0.68",
]
[[package]]
@ -2765,7 +2836,7 @@ dependencies = [
"clap_complete_nushell",
"regex",
"serde",
"vergen",
"vergen-gitcl",
"yazi-adapter",
"yazi-config",
"yazi-shared",
@ -2785,7 +2856,7 @@ dependencies = [
"serde_json",
"tokio",
"toml_edit",
"vergen",
"vergen-gitcl",
"yazi-dds",
"yazi-shared",
]
@ -2796,7 +2867,7 @@ version = "0.2.5"
dependencies = [
"anyhow",
"arc-swap",
"bitflags 2.5.0",
"bitflags 2.6.0",
"crossterm",
"globset",
"indexmap",
@ -2812,7 +2883,7 @@ name = "yazi-core"
version = "0.2.5"
dependencies = [
"anyhow",
"bitflags 2.5.0",
"bitflags 2.6.0",
"crossterm",
"dirs",
"futures",
@ -2852,7 +2923,7 @@ dependencies = [
"tokio-stream",
"tokio-util",
"uzers",
"vergen",
"vergen-gitcl",
"yazi-boot",
"yazi-shared",
]
@ -2962,7 +3033,7 @@ name = "yazi-shared"
version = "0.2.5"
dependencies = [
"anyhow",
"bitflags 2.5.0",
"bitflags 2.6.0",
"crossterm",
"dirs",
"futures",
@ -2979,22 +3050,22 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.7.34"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.34"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.67",
"syn 2.0.68",
]
[[package]]

View file

@ -15,12 +15,12 @@ yazi-config = { path = "../yazi-config", version = "0.2.5" }
yazi-shared = { path = "../yazi-shared", version = "0.2.5" }
# External dependencies
clap = { version = "4.5.7", features = [ "derive" ] }
serde = { version = "1.0.203", features = [ "derive" ] }
clap = { version = "4.5.9", features = [ "derive" ] }
serde = { version = "1.0.204", features = [ "derive" ] }
[build-dependencies]
clap = { version = "4.5.7", features = [ "derive" ] }
clap_complete = "4.5.6"
clap_complete_nushell = "4.5.2"
clap = { version = "4.5.9", features = [ "derive" ] }
clap_complete = "4.5.8"
clap_complete_fig = "4.5.1"
vergen = { version = "8.3.1", features = [ "build", "git", "gitcl" ] }
clap_complete_nushell = "4.5.2"
vergen-gitcl = { version = "1.0.0", features = [ "build" ] }

View file

@ -5,10 +5,13 @@ use std::{env, error::Error};
use clap::CommandFactory;
use clap_complete::{generate_to, Shell};
use vergen::EmitBuilder;
use vergen_gitcl::{BuildBuilder, Emitter, GitclBuilder};
fn main() -> Result<(), Box<dyn Error>> {
EmitBuilder::builder().build_date().git_sha(true).emit()?;
Emitter::default()
.add_instructions(&BuildBuilder::default().build_date(true).build()?)?
.add_instructions(&GitclBuilder::default().commit_date(true).sha(true).build()?)?
.emit()?;
if env::var_os("YAZI_GEN_COMPLETIONS").is_none() {
return Ok(());

View file

@ -14,21 +14,21 @@ yazi-shared = { path = "../yazi-shared", version = "0.2.5" }
# External dependencies
anyhow = "1.0.86"
clap = { version = "4.5.7", features = [ "derive" ] }
clap = { version = "4.5.9", features = [ "derive" ] }
crossterm = "0.27.0"
md-5 = "0.10.6"
serde_json = "1.0.117"
serde_json = "1.0.120"
tokio = { version = "1.38.0", features = [ "full" ] }
toml_edit = "0.22.14"
toml_edit = "0.22.15"
[build-dependencies]
anyhow = "1.0.86"
clap = { version = "4.5.7", features = [ "derive" ] }
clap_complete = "4.5.6"
clap = { version = "4.5.9", features = [ "derive" ] }
clap_complete = "4.5.8"
clap_complete_fig = "4.5.1"
clap_complete_nushell = "4.5.2"
serde_json = "1.0.117"
vergen = { version = "8.3.1", features = [ "build", "git", "gitcl" ] }
serde_json = "1.0.120"
vergen-gitcl = { version = "1.0.0", features = [ "build" ] }
[[bin]]
name = "ya"

View file

@ -5,10 +5,13 @@ use std::{env, error::Error};
use clap::CommandFactory;
use clap_complete::{generate_to, Shell};
use vergen::EmitBuilder;
use vergen_gitcl::{BuildBuilder, Emitter, GitclBuilder};
fn main() -> Result<(), Box<dyn Error>> {
EmitBuilder::builder().build_date().git_sha(true).emit()?;
Emitter::default()
.add_instructions(&BuildBuilder::default().build_date(true).build()?)?
.add_instructions(&GitclBuilder::default().commit_date(true).sha(true).build()?)?
.emit()?;
if env::var_os("YAZI_GEN_COMPLETIONS").is_none() {
return Ok(());

View file

@ -14,11 +14,11 @@ yazi-shared = { path = "../yazi-shared", version = "0.2.5" }
# External dependencies
anyhow = "1.0.86"
arc-swap = "1.7.1"
bitflags = "2.5.0"
bitflags = "2.6.0"
crossterm = "0.27.0"
globset = "0.4.14"
indexmap = "2.2.6"
ratatui = "0.27.0"
serde = { version = "1.0.203", features = [ "derive" ] }
serde = { version = "1.0.204", features = [ "derive" ] }
toml = { version = "0.8.14", features = [ "preserve_order" ] }
validator = { version = "0.18.1", features = [ "derive" ] }

View file

@ -773,9 +773,9 @@ conds = [
{ if = "sticky", text = "" },
# Fallback
{ if = "dir", text = "" },
{ if = "exec", text = "" },
{ if = "!dir", text = "" },
{ if = "dir", text = "󰉋" },
{ if = "exec", text = "" },
{ if = "!dir", text = "󰈔" },
]
# : }}}

View file

@ -122,7 +122,7 @@ previewers = [
]
[input]
cursor_blink = true
cursor_blink = false
# cd
cd_title = "Change directory:"

View file

@ -1,12 +1,7 @@
use ratatui::layout::Rect;
#[derive(Default)]
#[derive(Clone, Copy, Default)]
pub struct Layout {
pub header: Rect,
pub parent: Rect,
pub current: Rect,
pub preview: Rect,
pub status: Rect,
}

View file

@ -61,13 +61,20 @@ pub fn init() -> anyhow::Result<()> {
// TODO: Remove in v0.3.2
for c in &KEYMAP.manager {
for r in &c.run {
if r.name == "shell" && !r.bool("confirm") && !r.bool("interactive") {
if r.name != "shell" {
continue;
}
if !r.bool("confirm") && !r.bool("interactive") {
eprintln!(
r#"WARNING: In Yazi v0.3, the behavior of the interactive `shell` (i.e., shell templates) must be explicitly specified with `--interactive`.
Please replace e.g. `shell` with `shell --interactive`, `shell "my-template"` with `shell "my-template" --interactive`, in your keymap.toml"#
);
return Ok(());
} else if r.bool("confirm") && r.bool("interactive") {
eprintln!(
"The `shell` command cannot specify both `--confirm` and `--interactive` at the same time.",
);
}
}
}

View file

@ -20,7 +20,7 @@ yazi-shared = { path = "../yazi-shared", version = "0.2.5" }
# External dependencies
anyhow = "1.0.86"
bitflags = "2.5.0"
bitflags = "2.6.0"
crossterm = "0.27.0"
dirs = "5.0.1"
futures = "0.3.30"
@ -29,7 +29,7 @@ parking_lot = "0.12.3"
ratatui = "0.27.0"
regex = "1.10.5"
scopeguard = "1.2.0"
serde = "1.0.203"
serde = "1.0.204"
shell-words = "1.1.0"
tokio = { version = "1.38.0", features = [ "full" ] }
tokio-stream = "0.1.15"

View file

@ -20,14 +20,14 @@ yazi-shared = { path = "../yazi-shared", version = "0.2.5" }
anyhow = "1.0.86"
mlua = { version = "0.9.9", features = [ "lua54" ] }
parking_lot = "0.12.3"
serde = { version = "1.0.203", features = [ "derive" ] }
serde_json = "1.0.117"
serde = { version = "1.0.204", features = [ "derive" ] }
serde_json = "1.0.120"
tokio = { version = "1.38.0", features = [ "full" ] }
tokio-stream = "0.1.15"
tokio-util = "0.7.11"
[build-dependencies]
vergen = { version = "8.3.1", features = [ "build", "git", "gitcl" ] }
vergen-gitcl = { version = "1.0.0", features = [ "build" ] }
[target."cfg(unix)".dependencies]
uzers = "0.12.0"

View file

@ -1,9 +1,11 @@
use std::error::Error;
use vergen::EmitBuilder;
use vergen_gitcl::{Emitter, GitclBuilder};
fn main() -> Result<(), Box<dyn Error>> {
EmitBuilder::builder().git_sha(true).emit()?;
Emitter::default()
.add_instructions(&GitclBuilder::default().commit_date(true).sha(true).build()?)?
.emit()?;
Ok(())
}

View file

@ -52,5 +52,5 @@ name = "yazi"
path = "src/main.rs"
[package.metadata.binstall]
pkg-url = "{ repo }/releases/download/v{ version }/yazi-{ target }{ archive-suffix }"
bin-dir = "yazi-{ target }/{ bin }{ binary-ext }"
pkg-url = "{repo}/releases/download/v{version}/yazi-{target}{archive-suffix}"
bin-dir = "yazi-{target}/{bin}{binary-ext}"

View file

@ -1,11 +1,10 @@
use crossterm::event::{MouseEvent, MouseEventKind};
use mlua::Table;
use ratatui::layout::{Position, Rect};
use mlua::{Table, TableExt};
use tracing::error;
use yazi_config::{LAYOUT, MANAGER};
use yazi_config::MANAGER;
use yazi_plugin::{bindings::Cast, LUA};
use crate::{app::App, components, lives::Lives};
use crate::{app::App, lives::Lives};
pub struct Opt {
event: MouseEvent,
@ -19,47 +18,36 @@ impl App {
pub(crate) fn mouse(&mut self, opt: impl Into<Opt>) {
let event = (opt.into() as Opt).event;
let layout = LAYOUT.load();
let position = Position { x: event.column, y: event.row };
let Some(size) = self.term.as_ref().and_then(|t| t.size().ok()) else { return };
let Ok(evt) = yazi_plugin::bindings::MouseEvent::cast(&LUA, event) else { return };
if matches!(event.kind, MouseEventKind::Moved | MouseEventKind::Drag(_)) {
self.mouse_do(crate::Root::mouse, event, None);
return;
}
let res = Lives::scope(&self.cx, move |_| {
let area = yazi_plugin::elements::Rect::cast(&LUA, size)?;
let root = LUA.globals().raw_get::<_, Table>("Root")?.call_method::<_, Table>("new", area)?;
if layout.current.contains(position) {
self.mouse_do(components::Current::mouse, event, Some(layout.current));
} else if layout.preview.contains(position) {
self.mouse_do(components::Preview::mouse, event, Some(layout.preview));
} else if layout.parent.contains(position) {
self.mouse_do(components::Parent::mouse, event, Some(layout.parent));
} else if layout.header.contains(position) {
self.mouse_do(components::Header::mouse, event, Some(layout.header));
} else if layout.status.contains(position) {
self.mouse_do(components::Status::mouse, event, Some(layout.status));
}
}
fn mouse_do(
&self,
f: impl FnOnce(MouseEvent) -> mlua::Result<()>,
mut event: MouseEvent,
rect: Option<Rect>,
) {
if matches!(event.kind, MouseEventKind::Down(_) if MANAGER.mouse_events.draggable()) {
let evt = yazi_plugin::bindings::MouseEvent::cast(&LUA, event);
if let (Ok(evt), Ok(root)) = (evt, LUA.globals().raw_get::<_, Table>("Root")) {
root.raw_set("drag_start", evt).ok();
if matches!(event.kind, MouseEventKind::Down(_) if MANAGER.mouse_events.draggable()) {
root.raw_set("_drag_start", evt.clone())?;
}
}
if let Some(rect) = rect {
event.row -= rect.y;
event.column -= rect.x;
}
match event.kind {
MouseEventKind::Down(_) => root.call_method("click", (evt, false))?,
MouseEventKind::Up(_) => root.call_method("click", (evt, true))?,
if let Err(e) = Lives::scope(&self.cx, move |_| f(event)) {
error!("{:?}", e);
MouseEventKind::ScrollDown => root.call_method("scroll", (evt, 1))?,
MouseEventKind::ScrollUp => root.call_method("scroll", (evt, -1))?,
MouseEventKind::ScrollRight => root.call_method("touch", (evt, 1))?,
MouseEventKind::ScrollLeft => root.call_method("touch", (evt, -1))?,
MouseEventKind::Moved => root.call_method("move", evt)?,
MouseEventKind::Drag(_) => root.call_method("drag", evt)?,
}
Ok(())
});
if let Err(e) = res {
error!("{e}");
}
}
}

View file

@ -1,23 +0,0 @@
use crossterm::event::MouseEventKind;
use mlua::{Table, TableExt};
use yazi_plugin::{bindings::{Cast, MouseEvent}, LUA};
pub(crate) struct Current;
impl Current {
pub(crate) fn mouse(event: crossterm::event::MouseEvent) -> mlua::Result<()> {
let evt = MouseEvent::cast(&LUA, event)?;
let comp: Table = LUA.globals().raw_get("Current")?;
match event.kind {
MouseEventKind::Down(_) => comp.call_method("click", (evt, false))?,
MouseEventKind::Up(_) => comp.call_method("click", (evt, true))?,
MouseEventKind::ScrollDown => comp.call_method("scroll", (evt, 1))?,
MouseEventKind::ScrollUp => comp.call_method("scroll", (evt, -1))?,
MouseEventKind::ScrollRight => comp.call_method("touch", (evt, 1))?,
MouseEventKind::ScrollLeft => comp.call_method("touch", (evt, -1))?,
_ => (),
}
Ok(())
}
}

View file

@ -1,39 +0,0 @@
use crossterm::event::MouseEventKind;
use mlua::{Table, TableExt};
use ratatui::{buffer::Buffer, widgets::Widget};
use tracing::error;
use yazi_plugin::{bindings::{Cast, MouseEvent}, elements::{render_widgets, Rect}, LUA};
pub(crate) struct Header;
impl Widget for Header {
fn render(self, area: ratatui::layout::Rect, buf: &mut Buffer) {
let mut f = || {
let area = Rect::cast(&LUA, area)?;
let comp: Table = LUA.globals().raw_get("Header")?;
render_widgets(comp.call_method("render", area)?, buf);
Ok::<_, anyhow::Error>(())
};
if let Err(e) = f() {
error!("{:?}", e);
}
}
}
impl Header {
pub(crate) fn mouse(event: crossterm::event::MouseEvent) -> mlua::Result<()> {
let evt = MouseEvent::cast(&LUA, event)?;
let comp: Table = LUA.globals().raw_get("Header")?;
match event.kind {
MouseEventKind::Down(_) => comp.call_method("click", (evt, false))?,
MouseEventKind::Up(_) => comp.call_method("click", (evt, true))?,
MouseEventKind::ScrollDown => comp.call_method("scroll", (evt, 1))?,
MouseEventKind::ScrollUp => comp.call_method("scroll", (evt, -1))?,
MouseEventKind::ScrollRight => comp.call_method("touch", (evt, 1))?,
MouseEventKind::ScrollLeft => comp.call_method("touch", (evt, -1))?,
_ => (),
}
Ok(())
}
}

View file

@ -1,20 +0,0 @@
use mlua::{Table, TableExt};
use ratatui::{buffer::Buffer, widgets::Widget};
use tracing::error;
use yazi_plugin::{bindings::Cast, elements::{render_widgets, Rect}, LUA};
pub(crate) struct Manager;
impl Widget for Manager {
fn render(self, area: ratatui::layout::Rect, buf: &mut Buffer) {
let mut f = || {
let area = Rect::cast(&LUA, area)?;
let comp: Table = LUA.globals().raw_get("Manager")?;
render_widgets(comp.call_method("render", area)?, buf);
Ok::<_, anyhow::Error>(())
};
if let Err(e) = f() {
error!("{:?}", e);
}
}
}

View file

@ -1,17 +1,7 @@
#![allow(clippy::module_inception)]
mod current;
mod header;
mod manager;
mod parent;
mod preview;
mod progress;
mod status;
pub(super) use current::*;
pub(super) use header::*;
pub(super) use manager::*;
pub(super) use parent::*;
pub(super) use preview::*;
pub(super) use progress::*;
pub(super) use status::*;

View file

@ -1,23 +0,0 @@
use crossterm::event::MouseEventKind;
use mlua::{Table, TableExt};
use yazi_plugin::{bindings::{Cast, MouseEvent}, LUA};
pub(crate) struct Parent;
impl Parent {
pub(crate) fn mouse(event: crossterm::event::MouseEvent) -> mlua::Result<()> {
let evt = MouseEvent::cast(&LUA, event)?;
let comp: Table = LUA.globals().raw_get("Parent")?;
match event.kind {
MouseEventKind::Down(_) => comp.call_method("click", (evt, false))?,
MouseEventKind::Up(_) => comp.call_method("click", (evt, true))?,
MouseEventKind::ScrollDown => comp.call_method("scroll", (evt, 1))?,
MouseEventKind::ScrollUp => comp.call_method("scroll", (evt, -1))?,
MouseEventKind::ScrollRight => comp.call_method("touch", (evt, 1))?,
MouseEventKind::ScrollLeft => comp.call_method("touch", (evt, -1))?,
_ => (),
}
Ok(())
}
}

View file

@ -1,7 +1,4 @@
use crossterm::event::MouseEventKind;
use mlua::{Table, TableExt};
use ratatui::{buffer::Buffer, widgets::Widget};
use yazi_plugin::{bindings::{Cast, MouseEvent}, LUA};
use crate::Ctx;
@ -12,22 +9,6 @@ pub(crate) struct Preview<'a> {
impl<'a> Preview<'a> {
#[inline]
pub(crate) fn new(cx: &'a Ctx) -> Self { Self { cx } }
pub(crate) fn mouse(event: crossterm::event::MouseEvent) -> mlua::Result<()> {
let evt = MouseEvent::cast(&LUA, event)?;
let comp: Table = LUA.globals().raw_get("Preview")?;
match event.kind {
MouseEventKind::Down(_) => comp.call_method("click", (evt, false))?,
MouseEventKind::Up(_) => comp.call_method("click", (evt, true))?,
MouseEventKind::ScrollDown => comp.call_method("scroll", (evt, 1))?,
MouseEventKind::ScrollUp => comp.call_method("scroll", (evt, -1))?,
MouseEventKind::ScrollRight => comp.call_method("touch", (evt, 1))?,
MouseEventKind::ScrollLeft => comp.call_method("touch", (evt, -1))?,
_ => (),
}
Ok(())
}
}
impl Widget for Preview<'_> {

View file

@ -14,7 +14,7 @@ impl Progress {
let mut f = || {
let comp: Table = LUA.globals().raw_get("Progress")?;
for widget in comp.call_method::<_, Vec<AnyUserData>>("partial_render", ())? {
let Some(w) = cast_to_renderable(widget) else { continue };
let Some(w) = cast_to_renderable(&widget) else { continue };
let area = w.area();
w.render(buf);
@ -34,7 +34,7 @@ impl Progress {
};
if let Err(e) = f() {
error!("{:?}", e);
error!("{e}");
}
patches
}

View file

@ -1,39 +0,0 @@
use crossterm::event::MouseEventKind;
use mlua::{Table, TableExt};
use ratatui::widgets::Widget;
use tracing::error;
use yazi_plugin::{bindings::{Cast, MouseEvent}, elements::{render_widgets, Rect}, LUA};
pub(crate) struct Status;
impl Widget for Status {
fn render(self, area: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer) {
let mut f = || {
let area = Rect::cast(&LUA, area)?;
let comp: Table = LUA.globals().raw_get("Status")?;
render_widgets(comp.call_method("render", area)?, buf);
Ok::<_, anyhow::Error>(())
};
if let Err(e) = f() {
error!("{:?}", e);
}
}
}
impl Status {
pub(crate) fn mouse(event: crossterm::event::MouseEvent) -> mlua::Result<()> {
let evt = MouseEvent::cast(&LUA, event)?;
let comp: Table = LUA.globals().raw_get("Status")?;
match event.kind {
MouseEventKind::Down(_) => comp.call_method("click", (evt, false))?,
MouseEventKind::Up(_) => comp.call_method("click", (evt, true))?,
MouseEventKind::ScrollDown => comp.call_method("scroll", (evt, 1))?,
MouseEventKind::ScrollUp => comp.call_method("scroll", (evt, -1))?,
MouseEventKind::ScrollRight => comp.call_method("touch", (evt, 1))?,
MouseEventKind::ScrollLeft => comp.call_method("touch", (evt, -1))?,
_ => (),
}
Ok(())
}
}

View file

@ -1,10 +1,9 @@
use std::{mem, sync::Arc};
use std::mem;
use mlua::{Scope, Table};
use mlua::Scope;
use scopeguard::defer;
use tracing::error;
use yazi_config::LAYOUT;
use yazi_plugin::{elements::RectRef, LUA};
use yazi_plugin::LUA;
use yazi_shared::RoCell;
use crate::Ctx;
@ -52,17 +51,7 @@ impl Lives {
])?,
)?;
let ret = f(scope)?;
LAYOUT.store(Arc::new(yazi_config::Layout {
header: *globals.raw_get::<_, Table>("Header")?.raw_get::<_, RectRef>("area")?,
parent: *globals.raw_get::<_, Table>("Parent")?.raw_get::<_, RectRef>("area")?,
current: *globals.raw_get::<_, Table>("Current")?.raw_get::<_, RectRef>("area")?,
preview: *globals.raw_get::<_, Table>("Preview")?.raw_get::<_, RectRef>("area")?,
status: *globals.raw_get::<_, Table>("Status")?.raw_get::<_, RectRef>("area")?,
}));
Ok(ret)
f(scope)
});
if let Err(ref e) = result {

View file

@ -53,7 +53,7 @@ async fn main() -> anyhow::Result<()> {
yazi_dds::init();
yazi_plugin::init();
yazi_plugin::init()?;
yazi_core::init();

View file

@ -1,7 +1,7 @@
use crossterm::event::MouseEventKind;
use mlua::{Table, TableExt};
use ratatui::{buffer::Buffer, layout::{Constraint, Layout, Rect}, widgets::Widget};
use yazi_plugin::{bindings::{Cast, MouseEvent}, LUA};
use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget};
use tracing::error;
use yazi_plugin::{bindings::Cast, elements::render_widgets, LUA};
use super::{completion, input, select, tasks, which};
use crate::{components, help, Ctx};
@ -16,13 +16,17 @@ impl<'a> Root<'a> {
impl<'a> Widget for Root<'a> {
fn render(self, area: Rect, buf: &mut Buffer) {
let chunks =
Layout::vertical([Constraint::Length(1), Constraint::Fill(1), Constraint::Length(1)])
.split(area);
let mut f = || {
let area = yazi_plugin::elements::Rect::cast(&LUA, area)?;
let root = LUA.globals().raw_get::<_, Table>("Root")?.call_method::<_, Table>("new", area)?;
render_widgets(root.call_method("render", ())?, buf);
Ok::<_, mlua::Error>(())
};
if let Err(e) = f() {
error!("Failed to render the `Root` component:\n{e}");
}
components::Header.render(chunks[0], buf);
components::Manager.render(chunks[1], buf);
components::Status.render(chunks[2], buf);
components::Preview::new(self.cx).render(area, buf);
if self.cx.tasks.visible {
@ -50,17 +54,3 @@ impl<'a> Widget for Root<'a> {
}
}
}
impl Root<'_> {
pub(super) fn mouse(event: crossterm::event::MouseEvent) -> mlua::Result<()> {
let evt = MouseEvent::cast(&LUA, event)?;
let comp: Table = LUA.globals().raw_get("Root")?;
match event.kind {
MouseEventKind::Moved => comp.call_method("move", evt)?,
MouseEventKind::Drag(_) => comp.call_method("drag", evt)?,
_ => (),
}
Ok(())
}
}

View file

@ -46,4 +46,4 @@ tracing = { version = "0.1.40", features = [ "max_level_debug", "release_max_lev
uzers = "0.12.0"
[target."cfg(windows)".dependencies]
clipboard-win = "5.3.1"
clipboard-win = "5.4.0"

View file

@ -0,0 +1,40 @@
-- TODO: remove this after 0.3.0 release
Manager = {}
Folder = {}
File = {}
local b = false
function __yazi_check_and_warn_deprecated_api()
if b then
return
end
local warn = function(name)
ya.notify {
title = "Deprecated API",
content = string.format(
[[The `%s` global variable has been removed in Yazi v0.3, please remove it from your `init.lua`.
See https://github.com/sxyazi/yazi/pull/1257 for details.]],
name
),
timeout = 20,
level = "warn",
}
end
b = true
for _ in pairs(Manager) do
warn("Manager")
break
end
for _ in pairs(Folder) do
warn("Folder")
break
end
for _ in pairs(File) do
warn("File")
break
end
end

View file

@ -1,59 +1,58 @@
Current = {
area = ui.Rect.default,
_id = "current",
}
function Current:empty(area)
local folder = Folder:by_kind(Folder.CURRENT)
function Current:new(area, tab)
return setmetatable({
_area = area,
_tab = tab,
_folder = tab.current,
}, { __index = self })
end
function Current:empty()
local line
if folder.files.filter then
if self._folder.files.filter then
line = ui.Line("No filter results")
else
line = ui.Line(folder.stage == "loading" and "Loading..." or "No items")
line = ui.Line(self._folder.stage == "loading" and "Loading..." or "No items")
end
return {
ui.Paragraph(area, { line }):align(ui.Paragraph.CENTER),
ui.Paragraph(self._area, { line }):align(ui.Paragraph.CENTER),
}
end
function Current:render(area)
self.area = area
local files = Folder:by_kind(Folder.CURRENT).window
function Current:render()
local files = self._folder.window
if #files == 0 then
return self:empty(area)
return self:empty()
end
local items, markers = {}, {}
for i, f in ipairs(files) do
items[#items + 1] = ui.ListItem(ui.Line(File:full(f))):style(File:style(f))
-- Yanked/marked/selected files
local marker = File:marker(f)
if marker ~= 0 then
markers[#markers + 1] = { i, marker }
end
local items = {}
for _, f in ipairs(files) do
items[#items + 1] = ui.ListItem(Entity:render(f)):style(Entity:style(f))
end
return ya.flat {
ui.List(area, items),
Folder:linemode(area, files),
Folder:markers(area, markers),
return {
ui.List(self._area, items),
ui.Paragraph(self._area, Linemode:render(files)):align(ui.Paragraph.RIGHT),
}
end
-- Mouse events
function Current:click(event, up)
if up or event.is_middle then
return
end
local f = Folder:by_kind(Folder.CURRENT)
if event.y > #f.window or not f.hovered then
local f = self._folder
local y = event.y - self._area.y + 1
if y > #f.window or not f.hovered then
return
end
ya.manager_emit("arrow", { event.y + f.offset - f.hovered.idx })
ya.manager_emit("arrow", { y + f.offset - f.hovered.idx })
if event.is_right then
ya.manager_emit("open", {})
end

View file

@ -0,0 +1,109 @@
Entity = {
_inc = 1000,
}
function Entity:style(file)
local style = file:style()
if not file:is_hovered() then
return style
elseif file:in_preview() then
return style and style:patch(THEME.manager.preview_hovered) or THEME.manager.preview_hovered
else
return style and style:patch(THEME.manager.hovered) or THEME.manager.hovered
end
end
function Entity:icon(file)
local icon = file:icon()
if not icon then
return ui.Line("")
elseif file:is_hovered() then
return ui.Line(" " .. icon.text .. " ")
else
return ui.Line(" " .. icon.text .. " "):style(icon.style)
end
end
function Entity:prefix(file)
local prefix = file:prefix() or ""
return ui.Line(prefix ~= "" and prefix .. "/" or "")
end
function Entity:highlights(file)
local name = file.name:gsub("\r", "?", 1)
local highlights = file:highlights()
if not highlights or #highlights == 0 then
return ui.Line(name)
end
local spans, last = {}, 0
for _, h in ipairs(highlights) do
if h[1] > last then
spans[#spans + 1] = ui.Span(name:sub(last + 1, h[1]))
end
spans[#spans + 1] = ui.Span(name:sub(h[1] + 1, h[2])):style(THEME.manager.find_keyword)
last = h[2]
end
if last < #name then
spans[#spans + 1] = ui.Span(name:sub(last + 1))
end
return ui.Line(spans)
end
function Entity:found(file)
if not file:is_hovered() then
return ui.Line {}
end
local found = file:found()
if not found then
return ui.Line {}
end
return ui.Line {
ui.Span(" "),
ui.Span(string.format("[%d/%d]", found[1] + 1, found[2])):style(THEME.manager.find_position),
}
end
function Entity:symlink(file)
if not MANAGER.show_symlink then
return ui.Line {}
end
local to = file.link_to
return ui.Line(to and { ui.Span(" -> " .. tostring(to)):italic() } or {})
end
function Entity:render(file)
local lines = {}
for _, child in ipairs(self._children) do
lines[#lines + 1] = child[1](self, file)
end
return ui.Line(lines)
end
-- Initialize children
Entity._children = {
{ Entity.icon, id = 1, order = 1000 },
{ Entity.prefix, id = 2, order = 2000 },
{ Entity.highlights, id = 3, order = 3000 },
{ Entity.found, id = 4, order = 4000 },
{ Entity.symlink, id = 5, order = 5000 },
}
function Entity:children_add(fn, order)
self._inc = self._inc + 1
self._children[#self._children + 1] = { fn, id = self._inc, order = order }
table.sort(self._children, function(a, b) return a.order < b.order end)
return self._inc
end
function Entity:children_remove(id)
for i, child in ipairs(self._children) do
if child.id == id then
table.remove(self._children, i)
break
end
end
end

View file

@ -1,99 +0,0 @@
File = {}
function File:icon(file)
local icon = file:icon()
if not icon then
return {}
elseif file:is_hovered() then
return { ui.Span(" " .. icon.text .. " ") }
else
return { ui.Span(" " .. icon.text .. " "):style(icon.style) }
end
end
function File:prefix(file)
local prefix = file:prefix() or ""
return prefix == "" and {} or { ui.Span(prefix .. "/") }
end
function File:highlights(file)
local name = file.name:gsub("\r", "?", 1)
local highlights = file:highlights()
if not highlights or #highlights == 0 then
return { ui.Span(name) }
end
local spans, last = {}, 0
for _, h in ipairs(highlights) do
if h[1] > last then
spans[#spans + 1] = ui.Span(name:sub(last + 1, h[1]))
end
spans[#spans + 1] = ui.Span(name:sub(h[1] + 1, h[2])):style(THEME.manager.find_keyword)
last = h[2]
end
if last < #name then
spans[#spans + 1] = ui.Span(name:sub(last + 1))
end
return spans
end
function File:found(file)
if not file:is_hovered() then
return {}
end
local found = file:found()
if not found then
return {}
end
return {
ui.Span(" "),
ui.Span(string.format("[%d/%d]", found[1] + 1, found[2])):style(THEME.manager.find_position),
}
end
function File:symlink(file)
if not MANAGER.show_symlink then
return {}
end
local to = file.link_to
return to and { ui.Span(" -> " .. tostring(to)):italic() } or {}
end
function File:full(file)
return ya.flat {
self:icon(file),
self:prefix(file),
self:highlights(file),
self:found(file),
self:symlink(file),
}
end
function File:style(file)
local style = file:style()
if not file:is_hovered() then
return style
elseif file:in_preview() then
return style and style:patch(THEME.manager.preview_hovered) or THEME.manager.preview_hovered
else
return style and style:patch(THEME.manager.hovered) or THEME.manager.hovered
end
end
function File:marker(file)
local yanked = file:is_yanked()
if yanked ~= 0 then
return yanked -- 1: copied, 2: cut
end
local marked = file:is_marked()
if marked == 1 then
return 3 -- 3: marked
elseif marked == 0 and file:is_selected() then
return 4 -- 4: selected
end
return 0
end

View file

@ -1,92 +0,0 @@
Folder = {
PARENT = 0,
CURRENT = 1,
PREVIEW = 2,
}
function Folder:linemode(area, files)
local mode = cx.active.conf.linemode
if mode == "none" then
return {}
end
local lines = {}
for _, f in ipairs(files) do
local spans = { ui.Span(" ") }
if mode == "size" then
local size = f:size()
spans[#spans + 1] = ui.Span(size and ya.readable_size(size) or "")
elseif mode == "mtime" then
local time = f.cha.modified
spans[#spans + 1] = ui.Span(time and os.date("%y-%m-%d %H:%M", time // 1) or "")
elseif mode == "permissions" then
spans[#spans + 1] = ui.Span(f.cha:permissions() or "")
elseif mode == "owner" then
spans[#spans + 1] = ya.user_name and ui.Span(ya.user_name(f.cha.uid) .. ":" .. ya.group_name(f.cha.gid))
or ui.Span("")
end
spans[#spans + 1] = ui.Span(" ")
lines[#lines + 1] = ui.Line(spans)
end
return ui.Paragraph(area, lines):align(ui.Paragraph.RIGHT)
end
function Folder:markers(area, markers)
if #markers == 0 or area.w * area.h == 0 then
return {}
end
local elements = {}
local append = function(last)
local y = math.min(area.y + last[1], area.y + area.h) - 1
local bar = ui.Bar(
ui.Rect {
x = math.max(0, area.x - 1),
y = y,
w = 1,
h = math.min(1 + last[2] - last[1], area.y + area.h - y),
},
ui.Bar.LEFT
)
if last[3] == 1 then
bar = bar:style(THEME.manager.marker_copied)
elseif last[3] == 2 then
bar = bar:style(THEME.manager.marker_cut)
elseif last[3] == 3 then
bar = bar:style(THEME.manager.marker_marked)
elseif last[3] == 4 then
bar = bar:style(THEME.manager.marker_selected)
end
elements[#elements + 1] = bar
end
local last = { markers[1][1], markers[1][1], markers[1][2] } -- start, end, type
for _, m in ipairs(markers) do
if m[1] - last[2] > 1 or last[3] ~= m[2] then
append(last)
last = { m[1], m[1], m[2] }
else
last[2] = m[1]
end
end
append(last)
return elements
end
function Folder:by_kind(kind)
if kind == self.PARENT then
return cx.active.parent
elseif kind == self.CURRENT then
return cx.active.current
elseif kind == self.PREVIEW then
return cx.active.preview.folder
end
end
function Folder:window(kind)
local folder = self:by_kind(kind)
return folder and folder.window
end

View file

@ -1,15 +1,22 @@
Header = {
area = ui.Rect.default,
_id = "header",
}
function Header:new(area, tab)
return setmetatable({
_area = area,
_tab = tab,
}, { __index = self })
end
function Header:cwd(max)
local s = ya.readable_path(tostring(cx.active.current.cwd)) .. self:flags()
local s = ya.readable_path(tostring(self._tab.current.cwd)) .. self:flags()
return ui.Span(ya.truncate(s, { max = max, rtl = true })):style(THEME.manager.cwd)
end
function Header:flags()
local cwd = cx.active.current.cwd
local filter = cx.active.current.files.filter
local cwd = self._tab.current.cwd
local filter = self._tab.current.files.filter
local s = cwd.is_search and string.format(" (search: %s", cwd:frag()) or ""
if not filter then
@ -26,7 +33,7 @@ function Header:count()
local count, style
if yanked == 0 then
count = #cx.active.selected
count = #self._tab.selected
style = THEME.manager.count_selected
elseif cx.yanked.is_cut then
count = yanked
@ -67,17 +74,16 @@ function Header:tabs()
return ui.Line(spans)
end
function Header:render(area)
self.area = area
function Header:render()
local right = ui.Line { self:count(), self:tabs() }
local left = ui.Line { self:cwd(math.max(0, area.w - right:width())) }
local left = ui.Line { self:cwd(math.max(0, self._area.w - right:width())) }
return {
ui.Paragraph(area, { left }),
ui.Paragraph(area, { right }):align(ui.Paragraph.RIGHT),
ui.Paragraph(self._area, { left }),
ui.Paragraph(self._area, { right }):align(ui.Paragraph.RIGHT),
}
end
-- Mouse events
function Header:click(event, up) end
function Header:scroll(event, step) end

View file

@ -0,0 +1,77 @@
Linemode = {
_inc = 1000,
}
function Linemode:solo(file)
local mode = cx.active.conf.linemode
if mode == "none" or mode == "solo" then
return ui.Line("")
end
if not self[mode] then
return ui.Line(" " .. mode .. " ")
end
return ui.Line {
ui.Span(" "),
self[mode](self, file),
ui.Span(" "),
}
end
function Linemode:size(file)
local size = file:size()
return ui.Line(size and ya.readable_size(size) or "")
end
function Linemode:mtime(file)
local time = file.cha.modified
return ui.Line(time and os.date("%y-%m-%d %H:%M", time // 1) or "")
end
function Linemode:permissions(file) return ui.Line(file.cha:permissions() or "") end
function Linemode:owner(file)
if not ya.user_name then
return ui.Line("")
else
return ui.Line(ya.user_name(file.cha.uid) .. ":" .. ya.group_name(file.cha.gid))
end
end
function Linemode:render(files)
local lines = {}
for _, f in ipairs(files) do
lines[#lines + 1] = self:children_render(f)
end
return lines
end
-- Initialize children
Linemode._children = {
{ Linemode.solo, id = 1, order = 1000 },
}
function Linemode:children_add(fn, order)
self._inc = self._inc + 1
self._children[#self._children + 1] = { fn, id = self._inc, order = order }
table.sort(self._children, function(a, b) return a.order < b.order end)
return self._inc
end
function Linemode:children_remove(id)
for i, child in ipairs(self._children) do
if child.id == id then
table.remove(self._children, i)
break
end
end
end
function Linemode:children_render(file)
local lines = {}
for _, child in ipairs(self._children) do
lines[#lines + 1] = child[1](self, file)
end
return ui.Line(lines)
end

View file

@ -1,33 +0,0 @@
Manager = {
area = ui.Rect.default,
}
function Manager:layout(area)
self.area = area
return ui.Layout()
:direction(ui.Layout.HORIZONTAL)
:constraints({
ui.Constraint.Ratio(MANAGER.ratio.parent, MANAGER.ratio.all),
ui.Constraint.Ratio(MANAGER.ratio.current, MANAGER.ratio.all),
ui.Constraint.Ratio(MANAGER.ratio.preview, MANAGER.ratio.all),
})
:split(area)
end
function Manager:render(area)
local chunks = self:layout(area)
return ya.flat {
-- Borders
ui.Bar(chunks[1], ui.Bar.RIGHT):symbol(THEME.manager.border_symbol):style(THEME.manager.border_style),
ui.Bar(chunks[3], ui.Bar.LEFT):symbol(THEME.manager.border_symbol):style(THEME.manager.border_style),
-- Parent
Parent:render(chunks[1]:padding(ui.Padding.x(1))),
-- Current
Current:render(chunks[2]),
-- Preview
Preview:render(chunks[3]:padding(ui.Padding.x(1))),
}
end

View file

@ -0,0 +1,71 @@
Marker = {
_id = "marker",
}
function Marker:new(area, folder)
return setmetatable({
_area = area,
_folder = folder,
}, { __index = self })
end
function Marker:render()
if self._area.w * self._area.h == 0 then
return {}
elseif not self._folder or #self._folder.window == 0 then
return {}
end
local elements = {}
local append = function(last)
if not last[3] then
return
end
local y = math.min(self._area.y + last[1], self._area.y + self._area.h) - 1
local rect = ui.Rect {
x = math.max(0, self._area.x - 1),
y = y,
w = 1,
h = math.min(1 + last[2] - last[1], self._area.y + self._area.h - y),
}
elements[#elements + 1] = ui.Bar(rect, ui.Bar.LEFT):style(last[3])
end
local last = { 0, 0, nil } -- start, end, style
for i, f in ipairs(self._folder.window) do
local style = self:style(f)
if i - last[2] > 1 or last[3] ~= style then
append(last)
last = { i, i, style }
else
last[2] = i
end
end
append(last)
return elements
end
function Marker:style(file)
local marked = file:is_marked()
if marked == 1 then
return THEME.manager.marker_marked
elseif marked == 0 and file:is_selected() then
return THEME.manager.marker_selected
end
local yanked = file:is_yanked()
if yanked == 1 then
return THEME.manager.marker_copied
elseif yanked == 2 then
return THEME.manager.marker_cut
end
end
-- Mouse events
function Marker:click(event, up) end
function Marker:scroll(event, step) end
function Marker:touch(event, step) end

View file

@ -1,40 +1,40 @@
Parent = {
area = ui.Rect.default,
_id = "parent",
}
function Parent:render(area)
self.area = area
function Parent:new(area, tab)
return setmetatable({
_area = area,
_tab = tab,
_folder = tab.parent,
}, { __index = self })
end
local folder = Folder:by_kind(Folder.PARENT)
if not folder then
function Parent:render()
if not self._folder then
return {}
end
local items, markers = {}, {}
for i, f in ipairs(folder.window) do
items[#items + 1] = ui.ListItem(ui.Line(File:full(f))):style(File:style(f))
-- Yanked/marked/selected files
local marker = File:marker(f)
if marker ~= 0 then
markers[#markers + 1] = { i, marker }
end
local items = {}
for _, f in ipairs(self._folder.window) do
items[#items + 1] = ui.ListItem(Entity:render(f)):style(Entity:style(f))
end
return ya.flat {
ui.List(area, items),
Folder:markers(area, markers),
return {
ui.List(self._area, items),
}
end
-- Mouse events
function Parent:click(event, up)
if up or not event.is_left then
return
end
local window = Folder:window(Folder.PARENT) or {}
if window[event.y] then
ya.manager_emit("reveal", { window[event.y].url })
local y = event.y - self._area.y + 1
local window = self._folder and self._folder.window or {}
if window[y] then
ya.manager_emit("reveal", { window[y].url })
else
ya.manager_emit("leave", {})
end

View file

@ -1,20 +1,27 @@
Preview = {
area = ui.Rect.default,
_id = "preview",
}
function Preview:render(area)
self.area = area
return {}
function Preview:new(area, tab)
return setmetatable({
_area = area,
_tab = tab,
_folder = tab.preview.folder,
}, { __index = self })
end
function Preview:render() return {} end
-- Mouse events
function Preview:click(event, up)
if up or not event.is_left then
return
end
local window = Folder:window(Folder.PREVIEW) or {}
if window[event.y] then
ya.manager_emit("reveal", { window[event.y].url })
local y = event.y - self._area.y + 1
local window = self._folder and self._folder.window or {}
if window[y] then
ya.manager_emit("reveal", { window[y].url })
else
ya.manager_emit("enter", {})
end

View file

@ -1,9 +1,9 @@
Progress = {
area = ui.Rect.default,
_area = ui.Rect.default, -- TODO: remove this
}
function Progress:render(area, offset)
self.area = ui.Rect {
self._area = ui.Rect {
x = math.max(0, area.w - offset - 21),
y = area.y,
w = ya.clamp(0, area.w - offset - 1, 20),
@ -15,16 +15,13 @@ end
-- Progress bars usually need frequent updates to report the latest task progress.
-- We use `partial_render()` to partially render it when there is progress change,
-- which has almost no cost compared to a full render by `render()`.
--
-- However, at this time, we can only access `cx.tasks`. If you need certain data from the complete `cx`,
-- just cache it to `self` during `render()`, and read it in `partial_render()` - this process is referred to as "composition".
function Progress:partial_render()
local progress = cx.tasks.progress
if progress.total == 0 then
return { ui.Paragraph(self.area, {}) }
return { ui.Paragraph(self._area, {}) }
end
local gauge = ui.Gauge(self.area)
local gauge = ui.Gauge(self._area)
if progress.fail == 0 then
gauge = gauge:gauge_style(THEME.status.progress_normal)
else

View file

@ -0,0 +1,36 @@
Rail = {
_id = "rail",
_area = ui.Rect.default,
}
function Rail:new(chunks, tab)
local me = setmetatable({ _chunks = chunks, _tab = tab }, { __index = self })
me:build()
return me
end
function Rail:build()
self._base = {
ui.Bar(self._chunks[1], ui.Bar.RIGHT):symbol(THEME.manager.border_symbol):style(THEME.manager.border_style),
ui.Bar(self._chunks[3], ui.Bar.LEFT):symbol(THEME.manager.border_symbol):style(THEME.manager.border_style),
}
self._children = {
Marker:new(self._chunks[1], self._tab.parent),
Marker:new(self._chunks[2], self._tab.current),
}
end
function Rail:render()
local children = self._base or {}
for _, child in ipairs(self._children) do
children = ya.list_merge(children, ya.render_with(child))
end
return children
end
-- Mouse events
function Rail:click(event, up) end
function Rail:scroll(event, step) end
function Rail:touch(event, step) end

View file

@ -1,7 +1,59 @@
Root = {
drag_start = ui.Rect.default,
_id = "root",
_drag_start = ui.Rect.default,
}
function Root:new(area)
local me = setmetatable({ _area = area }, { __index = self })
me:layout()
me:build()
return me
end
function Root:layout()
self._chunks = ui.Layout()
:direction(ui.Layout.VERTICAL)
:constraints({
ui.Constraint.Length(1),
ui.Constraint.Fill(1),
ui.Constraint.Length(1),
})
:split(self._area)
end
function Root:build()
self._children = {
Header:new(self._chunks[1], cx.active),
Tab:new(self._chunks[2], cx.active),
Status:new(self._chunks[3], cx.active),
}
end
function Root:render()
local children = self._base or {}
for _, child in ipairs(self._children) do
children = ya.list_merge(children, ya.render_with(child))
end
__yazi_check_and_warn_deprecated_api() -- TODO: remove this after 0.3.0 release
return children
end
-- Mouse events
function Root:click(event, up)
local c = ya.child_at(ui.Position { x = event.x, y = event.y }, self._children)
return c and c:click(event, up)
end
function Root:scroll(event, step)
local c = ya.child_at(ui.Position { x = event.x, y = event.y }, self._children)
return c and c:scroll(event, step)
end
function Root:touch(event, step)
local c = ya.child_at(ui.Position { x = event.x, y = event.y }, self._children)
return c and c:touch(event, step)
end
function Root:move(event) end
function Root:drag(event) end

View file

@ -1,11 +1,22 @@
Status = {
area = ui.Rect.default,
LEFT = 0,
RIGHT = 1,
_id = "status",
_inc = 1000,
}
function Status.style()
if cx.active.mode.is_select then
function Status:new(area, tab)
return setmetatable({
_area = area,
_tab = tab,
}, { __index = self })
end
function Status:style()
if self._tab.mode.is_select then
return THEME.status.mode_select
elseif cx.active.mode.is_unset then
elseif self._tab.mode.is_unset then
return THEME.status.mode_unset
else
return THEME.status.mode_normal
@ -13,12 +24,12 @@ function Status.style()
end
function Status:mode()
local mode = tostring(cx.active.mode):upper()
local mode = tostring(self._tab.mode):upper()
if mode == "UNSET" then
mode = "UN-SET"
end
local style = self.style()
local style = self:style()
return ui.Line {
ui.Span(THEME.status.separator_open):fg(style.bg),
ui.Span(" " .. mode .. " "):style(style),
@ -26,12 +37,12 @@ function Status:mode()
end
function Status:size()
local h = cx.active.current.hovered
local h = self._tab.current.hovered
if not h then
return ui.Line {}
end
local style = self.style()
local style = self:style()
return ui.Line {
ui.Span(" " .. ya.readable_size(h:size() or h.cha.length) .. " "):fg(style.bg):bg(THEME.status.separator_style.bg),
ui.Span(THEME.status.separator_close):fg(THEME.status.separator_style.fg),
@ -39,16 +50,16 @@ function Status:size()
end
function Status:name()
local h = cx.active.current.hovered
local h = self._tab.current.hovered
if not h then
return ui.Span("")
return ui.Line {}
end
return ui.Span(" " .. h.name)
return ui.Line(" " .. h.name)
end
function Status:permissions()
local h = cx.active.current.hovered
local h = self._tab.current.hovered
if not h then
return ui.Line {}
end
@ -78,8 +89,8 @@ end
function Status:percentage()
local percent = 0
local cursor = cx.active.current.cursor
local length = #cx.active.current.files
local cursor = self._tab.current.cursor
local length = #self._tab.current.files
if cursor ~= 0 and length ~= 0 then
percent = math.floor((cursor + 1) * 100 / length)
end
@ -92,7 +103,7 @@ function Status:percentage()
percent = string.format(" %3d%% ", percent)
end
local style = self.style()
local style = self:style()
return ui.Line {
ui.Span(" " .. THEME.status.separator_open):fg(THEME.status.separator_style.fg),
ui.Span(percent):fg(style.bg):bg(THEME.status.separator_style.bg),
@ -100,30 +111,69 @@ function Status:percentage()
end
function Status:position()
local cursor = cx.active.current.cursor
local length = #cx.active.current.files
local cursor = self._tab.current.cursor
local length = #self._tab.current.files
local style = self.style()
local style = self:style()
return ui.Line {
ui.Span(string.format(" %2d/%-2d ", cursor + 1, length)):style(style),
ui.Span(THEME.status.separator_close):fg(style.bg),
}
end
function Status:render(area)
self.area = area
local left = ui.Line { self:mode(), self:size(), self:name() }
local right = ui.Line { self:permissions(), self:percentage(), self:position() }
function Status:render()
local left = self:children_render(self.LEFT)
local right = self:children_render(self.RIGHT)
return {
ui.Paragraph(area, { left }),
ui.Paragraph(area, { right }):align(ui.Paragraph.RIGHT),
table.unpack(Progress:render(area, right:width())),
ui.Paragraph(self._area, { left }),
ui.Paragraph(self._area, { right }):align(ui.Paragraph.RIGHT),
table.unpack(Progress:render(self._area, right:width())),
}
end
-- Mouse events
function Status:click(event, up) end
function Status:scroll(event, step) end
function Status:touch(event, step) end
-- Initialize children
Status._left = {
{ Status.mode, id = 1, order = 1000 },
{ Status.size, id = 2, order = 2000 },
{ Status.name, id = 3, order = 3000 },
}
Status._right = {
{ Status.permissions, id = 4, order = 1000 },
{ Status.percentage, id = 5, order = 2000 },
{ Status.position, id = 6, order = 3000 },
}
function Status:children_add(fn, order, side)
self._inc = self._inc + 1
local children = side == self.RIGHT and self._right or self._left
children[#children + 1] = { fn, id = self._inc, order = order }
table.sort(children, function(a, b) return a.order < b.order end)
return self._inc
end
function Status:children_remove(id, side)
local children = side == self.RIGHT and self._right or self._left
for i, child in ipairs(children) do
if child.id == id then
table.remove(children, i)
break
end
end
end
function Status:children_render(side)
local lines = {}
for _, child in ipairs(side == self.RIGHT and self._right or self._left) do
lines[#lines + 1] = child[1](self)
end
return ui.Line(lines)
end

View file

@ -0,0 +1,54 @@
Tab = {
_id = "tab",
}
function Tab:new(area, tab)
local me = setmetatable({ _area = area, _tab = tab }, { __index = self })
me:layout()
me:build()
return me
end
function Tab:layout()
self._chunks = ui.Layout()
:direction(ui.Layout.HORIZONTAL)
:constraints({
ui.Constraint.Ratio(MANAGER.ratio.parent, MANAGER.ratio.all),
ui.Constraint.Ratio(MANAGER.ratio.current, MANAGER.ratio.all),
ui.Constraint.Ratio(MANAGER.ratio.preview, MANAGER.ratio.all),
})
:split(self._area)
end
function Tab:build()
self._children = {
Rail:new(self._chunks, self._tab),
Parent:new(self._chunks[1]:padding(ui.Padding.x(1)), self._tab),
Current:new(self._chunks[2], self._tab),
Preview:new(self._chunks[3]:padding(ui.Padding.x(1)), self._tab),
}
end
function Tab:render()
local children = self._base or {}
for _, child in ipairs(self._children) do
children = ya.list_merge(children, ya.render_with(child))
end
return children
end
-- Mouse events
function Tab:click(event, up)
local c = ya.child_at(ui.Position { x = event.x, y = event.y }, self._children)
return c and c:click(event, up)
end
function Tab:scroll(event, step)
local c = ya.child_at(ui.Position { x = event.x, y = event.y }, self._children)
return c and c:scroll(event, step)
end
function Tab:touch(event, step)
local c = ya.child_at(ui.Position { x = event.x, y = event.y }, self._children)
return c and c:touch(event, step)
end

View file

@ -1,7 +1,7 @@
local M = {}
function M:peek()
local folder = Folder:by_kind(Folder.PREVIEW)
local folder = cx.active.preview.folder
if not folder or folder.cwd ~= self.file.url then
return
end
@ -18,28 +18,19 @@ function M:peek()
})
end
local items, markers = {}, {}
for i, f in ipairs(folder.window) do
items[#items + 1] = ui.ListItem(ui.Line(File:full(f))):style(File:style(f))
-- Yanked/marked/selected files
local marker = File:marker(f)
if marker ~= 0 then
markers[#markers + 1] = { i, marker }
end
local items = {}
for _, f in ipairs(folder.window) do
items[#items + 1] = ui.ListItem(Entity:render(f)):style(Entity:style(f))
end
ya.preview_widgets(
self,
ya.flat {
ui.List(self.area, items),
Folder:markers(self.area, markers),
}
)
ya.preview_widgets(self, {
ui.List(self.area, items),
table.unpack(Marker:new(self.area, folder):render()),
})
end
function M:seek(units)
local folder = Folder:by_kind(Folder.PREVIEW)
local folder = cx.active.preview.folder
if folder and folder.cwd == self.file.url then
local step = math.floor(units * self.area.h / 10)
local bound = math.max(0, #folder.files - self.area.h)

View file

@ -4,6 +4,8 @@ function M:peek() end
function M:seek() end
function M:fetch() return 1 end
function M:preload() return 1 end
return M

View file

@ -14,18 +14,11 @@ end
function ya.round(x) return x >= 0 and math.floor(x + 0.5) or math.ceil(x - 0.5) end
function ya.flat(t)
local r = {}
for _, v in ipairs(t) do
if type(v) == "table" then
for _, v2 in ipairs(ya.flat(v)) do
r[#r + 1] = v2
end
else
r[#r + 1] = v
end
function ya.list_merge(a, b)
for _, v in ipairs(b) do
a[#a + 1] = v
end
return r
return a
end
function ya.basename(str) return string.gsub(str, "(.*[/\\])(.*)", "%2") end
@ -50,3 +43,11 @@ function ya.readable_path(path)
return path
end
end
function ya.child_at(position, children)
for i = #children, 1, -1 do
if children[i]._area:contains(position) then
return children[i]
end
end
end

View file

@ -8,8 +8,8 @@ pub struct MouseEvent;
impl MouseEvent {
pub fn register(lua: &Lua) -> mlua::Result<()> {
lua.register_userdata_type::<crossterm::event::MouseEvent>(|reg| {
reg.add_field_method_get("x", |_, me| Ok(me.column as u32 + 1));
reg.add_field_method_get("y", |_, me| Ok(me.row as u32 + 1));
reg.add_field_method_get("x", |_, me| Ok(me.column));
reg.add_field_method_get("y", |_, me| Ok(me.row));
reg.add_field_method_get("is_left", |_, me| {
use crossterm::event::MouseEventKind as K;
Ok(matches!(me.kind, K::Down(b) | K::Up(b) | K::Drag(b) if b == MouseButton::Left))

View file

@ -2,7 +2,7 @@ use mlua::AnyUserData;
use crate::elements::Renderable;
pub fn cast_to_renderable(ud: AnyUserData) -> Option<Box<dyn Renderable + Send>> {
pub fn cast_to_renderable(ud: &AnyUserData) -> Option<Box<dyn Renderable + Send>> {
if let Ok(c) = ud.take::<crate::elements::Paragraph>() {
Some(Box::new(c))
} else if let Ok(c) = ud.take::<crate::elements::List>() {

View file

@ -7,6 +7,18 @@ impl Constraint {
pub fn install(lua: &Lua, ui: &Table) -> mlua::Result<()> {
let constraint = lua.create_table()?;
constraint.raw_set(
"Min",
lua.create_function(|_, n: u16| Ok(Constraint(ratatui::layout::Constraint::Min(n))))?,
)?;
constraint.raw_set(
"Max",
lua.create_function(|_, n: u16| Ok(Constraint(ratatui::layout::Constraint::Max(n))))?,
)?;
constraint.raw_set(
"Length",
lua.create_function(|_, n: u16| Ok(Constraint(ratatui::layout::Constraint::Length(n))))?,
)?;
constraint.raw_set(
"Percentage",
lua
@ -19,16 +31,8 @@ impl Constraint {
})?,
)?;
constraint.raw_set(
"Length",
lua.create_function(|_, n: u16| Ok(Constraint(ratatui::layout::Constraint::Length(n))))?,
)?;
constraint.raw_set(
"Max",
lua.create_function(|_, n: u16| Ok(Constraint(ratatui::layout::Constraint::Max(n))))?,
)?;
constraint.raw_set(
"Min",
lua.create_function(|_, n: u16| Ok(Constraint(ratatui::layout::Constraint::Min(n))))?,
"Fill",
lua.create_function(|_, n: u16| Ok(Constraint(ratatui::layout::Constraint::Fill(n))))?,
)?;
ui.raw_set("Constraint", constraint)

View file

@ -1,4 +1,5 @@
use mlua::{AnyUserData, Lua};
use mlua::{AnyUserData, Lua, Table};
use tracing::error;
use crate::cast_to_renderable;
@ -21,6 +22,7 @@ pub fn pour(lua: &Lua) -> mlua::Result<()> {
super::ListItem::install(lua, &ui)?;
super::Padding::install(lua, &ui)?;
super::Paragraph::install(lua, &ui)?;
super::Position::install(lua, &ui)?;
super::Rect::install(lua, &ui)?;
super::Span::install(lua, &ui)?;
super::Style::install(lua, &ui)?;
@ -36,10 +38,16 @@ pub trait Renderable {
fn clone_render(&self, buf: &mut ratatui::buffer::Buffer);
}
pub fn render_widgets(widgets: Vec<AnyUserData>, buf: &mut ratatui::buffer::Buffer) {
for widget in widgets {
if let Some(w) = cast_to_renderable(widget) {
w.render(buf);
pub fn render_widgets(widgets: Table, buf: &mut ratatui::buffer::Buffer) {
for widget in widgets.sequence_values::<AnyUserData>() {
let Ok(widget) = widget else {
error!("Failed to convert to renderable UserData: {}", widget.unwrap_err());
continue;
};
match cast_to_renderable(&widget) {
Some(w) => w.render(buf),
None => error!("Only the UserData of renderable element is accepted: {widget:#?}"),
}
}
}

View file

@ -23,7 +23,11 @@ impl TryFrom<Table<'_>> for Line {
if let Ok(span) = ud.take::<Span>() {
spans.push(span.0);
} else if let Ok(line) = ud.take::<Line>() {
spans.extend(line.0.spans.into_iter().collect::<Vec<_>>());
let style = line.0.style;
spans.extend(line.0.spans.into_iter().map(|mut span| {
span.style = style.patch(span.style);
span
}));
} else {
return Err("expected a table of Spans or Lines".into_lua_err());
}
@ -83,9 +87,9 @@ impl UserData for Line {
{
let mut me = ud.borrow_mut::<Self>()?;
me.0.style = match value {
Value::Nil => me.0.style.patch(ratatui::style::Style::reset()),
Value::Table(tb) => me.0.style.patch(Style::try_from(tb)?.0),
Value::UserData(ud) => me.0.style.patch(ud.borrow::<Style>()?.0),
Value::Nil => ratatui::style::Style::default(),
Value::Table(tb) => Style::try_from(tb)?.0,
Value::UserData(ud) => ud.borrow::<Style>()?.0,
_ => return Err("expected a Style or Table or nil".into_lua_err()),
};
}

View file

@ -11,6 +11,7 @@ mod line;
mod list;
mod padding;
mod paragraph;
mod position;
mod rect;
mod span;
mod style;
@ -26,6 +27,7 @@ pub use line::*;
pub use list::*;
pub use padding::*;
pub use paragraph::*;
pub use position::*;
pub use rect::*;
pub use span::*;
pub use style::*;

View file

@ -0,0 +1,38 @@
use mlua::{AnyUserData, Lua, Table, UserDataFields, UserDataRef};
use crate::bindings::Cast;
pub type PositionRef<'lua> = UserDataRef<'lua, ratatui::layout::Position>;
pub struct Position;
impl Position {
pub fn install(lua: &Lua, ui: &Table) -> mlua::Result<()> {
let new = lua.create_function(|lua, (_, args): (Table, Table)| {
Position::cast(lua, ratatui::layout::Position {
x: args.raw_get("x")?,
y: args.raw_get("y")?,
})
})?;
let position =
lua.create_table_from([("default", Position::cast(lua, Default::default())?)])?;
position.set_metatable(Some(lua.create_table_from([("__call", new)])?));
ui.raw_set("Position", position)
}
pub fn register(lua: &Lua) -> mlua::Result<()> {
lua.register_userdata_type::<ratatui::layout::Position>(|reg| {
reg.add_field_method_get("x", |_, me| Ok(me.x));
reg.add_field_method_get("y", |_, me| Ok(me.y));
})
}
}
impl Cast<ratatui::layout::Position> for Position {
fn cast(lua: &Lua, data: ratatui::layout::Position) -> mlua::Result<AnyUserData> {
lua.create_any_userdata(data)
}
}

View file

@ -1,6 +1,6 @@
use mlua::{AnyUserData, Lua, Table, UserDataFields, UserDataMethods, UserDataRef};
use super::PaddingRef;
use super::{PaddingRef, Position, PositionRef};
use crate::bindings::Cast;
pub type RectRef<'lua> = UserDataRef<'lua, ratatui::layout::Rect>;
@ -46,6 +46,8 @@ impl Rect {
r.height = r.height.saturating_sub(padding.top + padding.bottom);
Rect::cast(lua, r)
});
reg.add_method("position", |lua, me, ()| Position::cast(lua, me.as_position()));
reg.add_method("contains", |_, me, position: PositionRef| Ok(me.contains(*position)));
})
}
}

View file

@ -12,7 +12,7 @@ pub async fn entry(name: String, args: Vec<Data>) -> mlua::Result<()> {
tokio::task::spawn_blocking(move || {
let lua = slim_lua(&name)?;
let plugin: Table = if let Some(b) = LOADER.read().get(&name) {
lua.load(b.as_ref()).call(())?
lua.load(b.as_ref()).set_name(name).call(())?
} else {
return Err("unloaded plugin".into_lua_err());
};

View file

@ -12,7 +12,7 @@ pub async fn fetch(name: &str, files: Vec<yazi_shared::fs::File>) -> mlua::Resul
tokio::task::spawn_blocking(move || {
let lua = slim_lua(&name)?;
let plugin: Table = if let Some(b) = LOADER.read().get(&name) {
lua.load(b.as_ref()).call(())?
lua.load(b.as_ref()).set_name(name).call(())?
} else {
return Err("unloaded plugin".into_lua_err());
};

View file

@ -16,7 +16,7 @@ pub fn slim_lua(name: &str) -> mlua::Result<Lua> {
crate::process::install(&lua)?;
crate::utils::install_isolate(&lua)?;
crate::Config::new(&lua).install_preview()?;
lua.load(include_str!("../../preset/ya.lua")).exec()?;
lua.load(include_str!("../../preset/ya.lua")).set_name("ya.lua").exec()?;
// Elements
let ui = lua.create_table()?;

View file

@ -26,7 +26,7 @@ pub fn peek(cmd: &Cmd, file: yazi_shared::fs::File, skip: usize) -> Cancellation
);
let plugin: Table = if let Some(b) = LOADER.read().get(&name) {
lua.load(b.as_ref()).call(())?
lua.load(b.as_ref()).set_name(name).call(())?
} else {
return Err("unloaded plugin".into_lua_err());
};

View file

@ -12,7 +12,7 @@ pub async fn preload(name: &str, file: yazi_shared::fs::File) -> mlua::Result<u8
tokio::task::spawn_blocking(move || {
let lua = slim_lua(&name)?;
let plugin: Table = if let Some(b) = LOADER.read().get(&name) {
lua.load(b.as_ref()).call(())?
lua.load(b.as_ref()).set_name(name).call(())?
} else {
return Err("unloaded plugin".into_lua_err());
};

View file

@ -26,9 +26,10 @@ pub use lua::*;
pub use opt::*;
pub use runtime::*;
pub fn init() {
pub fn init() -> anyhow::Result<()> {
CLIPBOARD.with(Default::default);
crate::loader::init();
crate::init_lua();
crate::init_lua()?;
Ok(())
}

View file

@ -1,6 +1,6 @@
use std::{borrow::Cow, collections::HashMap, ops::Deref};
use anyhow::{bail, Result};
use anyhow::Result;
use mlua::{ExternalError, Table};
use parking_lot::RwLock;
use tokio::fs;
@ -22,36 +22,31 @@ impl Loader {
return Ok(());
}
let b = match name {
"dds" => Some(&include_bytes!("../../preset/plugins/dds.lua")[..]),
"noop" => Some(&include_bytes!("../../preset/plugins/noop.lua")[..]),
"session" => Some(&include_bytes!("../../preset/plugins/session.lua")[..]),
_ => None,
let preset = match name {
"dds" => &include_bytes!("../../preset/plugins/dds.lua")[..],
"noop" => include_bytes!("../../preset/plugins/noop.lua"),
"session" => include_bytes!("../../preset/plugins/session.lua"),
"archive" => include_bytes!("../../preset/plugins/archive.lua"),
"code" => include_bytes!("../../preset/plugins/code.lua"),
"file" => include_bytes!("../../preset/plugins/file.lua"),
"folder" => include_bytes!("../../preset/plugins/folder.lua"),
"font" => include_bytes!("../../preset/plugins/font.lua"),
"fzf" => include_bytes!("../../preset/plugins/fzf.lua"),
"image" => include_bytes!("../../preset/plugins/image.lua"),
"json" => include_bytes!("../../preset/plugins/json.lua"),
"magick" => include_bytes!("../../preset/plugins/magick.lua"),
"mime" => include_bytes!("../../preset/plugins/mime.lua"),
"pdf" => include_bytes!("../../preset/plugins/pdf.lua"),
"video" => include_bytes!("../../preset/plugins/video.lua"),
"zoxide" => include_bytes!("../../preset/plugins/zoxide.lua"),
_ => b"",
};
if let Some(b) = b {
self.cache.write().insert(name.to_owned(), Cow::Borrowed(b));
return Ok(());
}
let path = BOOT.plugin_dir.join(format!("{name}.yazi/init.lua"));
let b = fs::read(path).await.map(|v| v.into()).or_else(|_| {
Ok(Cow::from(match name {
"archive" => &include_bytes!("../../preset/plugins/archive.lua")[..],
"code" => include_bytes!("../../preset/plugins/code.lua"),
"file" => include_bytes!("../../preset/plugins/file.lua"),
"folder" => include_bytes!("../../preset/plugins/folder.lua"),
"font" => include_bytes!("../../preset/plugins/font.lua"),
"fzf" => include_bytes!("../../preset/plugins/fzf.lua"),
"image" => include_bytes!("../../preset/plugins/image.lua"),
"json" => include_bytes!("../../preset/plugins/json.lua"),
"magick" => include_bytes!("../../preset/plugins/magick.lua"),
"mime" => include_bytes!("../../preset/plugins/mime.lua"),
"pdf" => include_bytes!("../../preset/plugins/pdf.lua"),
"video" => include_bytes!("../../preset/plugins/video.lua"),
"zoxide" => include_bytes!("../../preset/plugins/zoxide.lua"),
_ => bail!("plugin not found: {name}"),
}))
})?;
let b = if preset.is_empty() {
Cow::Owned(fs::read(BOOT.plugin_dir.join(format!("{name}.yazi/init.lua"))).await?)
} else {
Cow::Borrowed(preset)
};
self.cache.write().insert(name.to_owned(), b);
Ok(())
@ -65,10 +60,11 @@ impl Loader {
}
let t: Table = match self.read().get(name) {
Some(b) => LUA.load(b.as_ref()).call(())?,
Some(b) => LUA.load(b.as_ref()).set_name(name).call(())?,
None => Err(format!("plugin `{name}` not found").into_lua_err())?,
};
// TODO: rename to `_id`
t.raw_set("_name", LUA.create_string(name)?)?;
loaded.raw_set(name, t.clone())?;
Ok(t)

View file

@ -1,4 +1,4 @@
use anyhow::Result;
use anyhow::{Context, Result};
use mlua::Lua;
use yazi_boot::BOOT;
use yazi_shared::RoCell;
@ -7,11 +7,12 @@ use crate::runtime::Runtime;
pub static LUA: RoCell<Lua> = RoCell::new();
pub(super) fn init_lua() {
pub(super) fn init_lua() -> Result<()> {
LUA.init(Lua::new());
stage_1(&LUA).expect("Lua setup failed");
stage_2(&LUA).expect("Lua runtime failed");
stage_1(&LUA).context("Lua setup failed")?;
stage_2(&LUA).context("Lua runtime failed")?;
Ok(())
}
fn stage_1(lua: &'static Lua) -> Result<()> {
@ -20,7 +21,7 @@ fn stage_1(lua: &'static Lua) -> Result<()> {
// Base
lua.set_named_registry_value("rt", Runtime::default())?;
lua.load(include_str!("../preset/ya.lua")).exec()?;
lua.load(include_str!("../preset/ya.lua")).set_name("ya.lua").exec()?;
crate::bindings::Icon::register(lua)?;
crate::bindings::MouseEvent::register(lua)?;
crate::elements::pour(lua)?;
@ -31,25 +32,28 @@ fn stage_1(lua: &'static Lua) -> Result<()> {
crate::url::pour(lua)?;
// Components
lua.load(include_str!("../preset/components/current.lua")).exec()?;
lua.load(include_str!("../preset/components/file.lua")).exec()?;
lua.load(include_str!("../preset/components/folder.lua")).exec()?;
lua.load(include_str!("../preset/components/header.lua")).exec()?;
lua.load(include_str!("../preset/components/manager.lua")).exec()?;
lua.load(include_str!("../preset/components/parent.lua")).exec()?;
lua.load(include_str!("../preset/components/preview.lua")).exec()?;
lua.load(include_str!("../preset/components/progress.lua")).exec()?;
lua.load(include_str!("../preset/components/root.lua")).exec()?;
lua.load(include_str!("../preset/components/status.lua")).exec()?;
lua.load(include_str!("../preset/components/current.lua")).set_name("current.lua").exec()?;
lua.load(include_str!("../preset/components/entity.lua")).set_name("entity.lua").exec()?;
lua.load(include_str!("../preset/components/header.lua")).set_name("header.lua").exec()?;
lua.load(include_str!("../preset/components/linemode.lua")).set_name("linemode.lua").exec()?;
lua.load(include_str!("../preset/components/marker.lua")).set_name("marker.lua").exec()?;
lua.load(include_str!("../preset/components/parent.lua")).set_name("parent.lua").exec()?;
lua.load(include_str!("../preset/components/preview.lua")).set_name("preview.lua").exec()?;
lua.load(include_str!("../preset/components/progress.lua")).set_name("progress.lua").exec()?;
lua.load(include_str!("../preset/components/rail.lua")).set_name("rail.lua").exec()?;
lua.load(include_str!("../preset/components/root.lua")).set_name("root.lua").exec()?;
lua.load(include_str!("../preset/components/status.lua")).set_name("status.lua").exec()?;
lua.load(include_str!("../preset/components/tab.lua")).set_name("tab.lua").exec()?;
Ok(())
}
fn stage_2(lua: &'static Lua) -> mlua::Result<()> {
lua.load(include_str!("../preset/setup.lua")).exec()?;
lua.load(include_str!("../preset/setup.lua")).set_name("setup.lua").exec()?;
lua.load(include_str!("../preset/compat.lua")).set_name("compat.lua").exec()?;
if let Ok(b) = std::fs::read(BOOT.config_dir.join("init.lua")) {
lua.load(b).exec()?;
lua.load(b).set_name("init.lua").exec()?;
}
Ok(())

View file

@ -1,10 +1,13 @@
use std::collections::HashMap;
use std::{collections::HashMap, sync::Arc};
use mlua::{ExternalError, Lua, Table, Value};
use mlua::{ExternalError, Lua, Table, TableExt, Value};
use tracing::error;
use yazi_config::LAYOUT;
use yazi_dds::Sendable;
use yazi_shared::{emit, event::{Cmd, Data}, render, Layer};
use super::Utils;
use crate::elements::RectRef;
impl Utils {
fn parse_args(t: Table) -> mlua::Result<HashMap<String, Data>> {
@ -33,6 +36,38 @@ impl Utils {
})?,
)?;
ya.raw_set(
"render_with",
lua.create_function(|lua, c: Table| {
let id: mlua::String = c.get("_id")?;
let id = id.to_str()?;
match id {
"current" => {
LAYOUT.store(Arc::new(yazi_config::Layout {
current: *c.raw_get::<_, RectRef>("_area")?,
..*LAYOUT.load_full()
}));
}
"preview" => {
LAYOUT.store(Arc::new(yazi_config::Layout {
preview: *c.raw_get::<_, RectRef>("_area")?,
..*LAYOUT.load_full()
}));
}
_ => {}
}
match c.call_method::<_, Table>("render", ()) {
Err(e) => {
error!("Failed to `render()` the `{id}` component:\n{e}");
lua.create_table()
}
ok => ok,
}
})?,
)?;
ya.raw_set(
"app_emit",
lua.create_function(|_, (name, args): (String, Table)| {

View file

@ -8,7 +8,7 @@ impl Utils {
ya.raw_set(
"dbg",
lua.create_function(|_, values: Variadic<Value>| {
let s = values.into_iter().map(|v| format!("{:#?}", v)).collect::<Vec<_>>().join(" ");
let s = values.into_iter().map(|v| format!("{v:#?}")).collect::<Vec<_>>().join(" ");
Ok(debug!("{s}"))
})?,
)?;
@ -16,7 +16,7 @@ impl Utils {
ya.raw_set(
"err",
lua.create_function(|_, values: Variadic<Value>| {
let s = values.into_iter().map(|v| format!("{:#?}", v)).collect::<Vec<_>>().join(" ");
let s = values.into_iter().map(|v| format!("{v:#?}")).collect::<Vec<_>>().join(" ");
Ok(error!("{s}"))
})?,
)?;

View file

@ -53,7 +53,7 @@ impl Utils {
"preview_widgets",
lua.create_async_function(|_, (t, widgets): (Table, Vec<AnyUserData>)| async move {
let mut lock = PreviewLock::try_from(t)?;
lock.data = widgets.into_iter().filter_map(cast_to_renderable).collect();
lock.data = widgets.into_iter().filter_map(|ud| cast_to_renderable(&ud)).collect();
emit!(Call(Cmd::new("preview").with_any("lock", lock), Layer::Manager));
Ok(())

View file

@ -11,7 +11,7 @@ rust-version = "1.78.0"
[dependencies]
anyhow = "1.0.86"
bitflags = "2.5.0"
bitflags = "2.6.0"
crossterm = "0.27.0"
dirs = "5.0.1"
futures = "0.3.30"
@ -19,7 +19,7 @@ parking_lot = "0.12.3"
percent-encoding = "2.3.1"
ratatui = "0.27.0"
regex = "1.10.5"
serde = { version = "1.0.203", features = [ "derive" ] }
serde = { version = "1.0.204", features = [ "derive" ] }
shell-words = "1.1.0"
tokio = { version = "1.38.0", features = [ "full" ] }