fix: use complete rendering instead of partial rendering for no task progress report (#1876)

This commit is contained in:
三咲雅 · Misaki Masa 2024-11-01 19:52:19 +08:00 committed by GitHub
parent a510673123
commit 0baccdc9aa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
45 changed files with 174 additions and 190 deletions

8
Cargo.lock generated
View file

@ -133,12 +133,6 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110"
[[package]]
name = "arc-swap"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
[[package]] [[package]]
name = "arg_enum_proc_macro" name = "arg_enum_proc_macro"
version = "0.3.4" version = "0.3.4"
@ -3177,7 +3171,6 @@ version = "0.3.3"
dependencies = [ dependencies = [
"ansi-to-tui", "ansi-to-tui",
"anyhow", "anyhow",
"arc-swap",
"base64", "base64",
"color_quant", "color_quant",
"crossterm", "crossterm",
@ -3244,7 +3237,6 @@ name = "yazi-config"
version = "0.3.3" version = "0.3.3"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"arc-swap",
"bitflags 2.6.0", "bitflags 2.6.0",
"crossterm", "crossterm",
"globset", "globset",

View file

@ -12,7 +12,6 @@ strip = true
[workspace.dependencies] [workspace.dependencies]
ansi-to-tui = "7.0.0" ansi-to-tui = "7.0.0"
anyhow = "1.0.91" anyhow = "1.0.91"
arc-swap = "1.7.1"
base64 = "0.22.1" base64 = "0.22.1"
bitflags = "2.6.0" bitflags = "2.6.0"
clap = { version = "4.5.20", features = [ "derive" ] } clap = { version = "4.5.20", features = [ "derive" ] }

View file

@ -16,7 +16,6 @@ yazi-shared = { path = "../yazi-shared", version = "0.3.3" }
# External dependencies # External dependencies
ansi-to-tui = { workspace = true } ansi-to-tui = { workspace = true }
anyhow = { workspace = true } anyhow = { workspace = true }
arc-swap = { workspace = true }
base64 = { workspace = true } base64 = { workspace = true }
color_quant = "1.1.0" color_quant = "1.1.0"
crossterm = { workspace = true } crossterm = { workspace = true }

View file

@ -1,4 +1,4 @@
use std::{env, fmt::Display, path::Path, sync::Arc}; use std::{env, fmt::Display, path::Path};
use anyhow::Result; use anyhow::Result;
use ratatui::layout::Rect; use ratatui::layout::Rect;
@ -52,7 +52,7 @@ impl Adapter {
} }
pub fn image_hide(self) -> Result<()> { pub fn image_hide(self) -> Result<()> {
if let Some(area) = SHOWN.swap(None) { self.image_erase(*area) } else { Ok(()) } if let Some(area) = SHOWN.replace(None) { self.image_erase(area) } else { Ok(()) }
} }
pub fn image_erase(self, area: Rect) -> Result<()> { pub fn image_erase(self, area: Rect) -> Result<()> {
@ -67,10 +67,10 @@ impl Adapter {
} }
#[inline] #[inline]
pub fn shown_load(self) -> Option<Rect> { SHOWN.load_full().map(|r| *r) } pub fn shown_load(self) -> Option<Rect> { SHOWN.get() }
#[inline] #[inline]
pub(super) fn shown_store(area: Rect) { SHOWN.store(Some(Arc::new(area))); } pub(super) fn shown_store(area: Rect) { SHOWN.set(Some(area)); }
pub(super) fn start(self) { Ueberzug::start(self); } pub(super) fn start(self) { Ueberzug::start(self); }

View file

@ -4,7 +4,7 @@ yazi_macro::mod_flat!(
adapter chafa dimension emulator iip image kgp kgp_old mux sixel ueberzug adapter chafa dimension emulator iip image kgp kgp_old mux sixel ueberzug
); );
use yazi_shared::{RoCell, env_exists, in_wsl}; use yazi_shared::{RoCell, SyncCell, env_exists, in_wsl};
pub static ADAPTOR: RoCell<Adapter> = RoCell::new(); pub static ADAPTOR: RoCell<Adapter> = RoCell::new();
// Tmux support // Tmux support
@ -17,7 +17,7 @@ static CLOSE: RoCell<&'static str> = RoCell::new();
pub static WSL: RoCell<bool> = RoCell::new(); pub static WSL: RoCell<bool> = RoCell::new();
// Image state // Image state
static SHOWN: RoCell<arc_swap::ArcSwapOption<ratatui::layout::Rect>> = RoCell::new(); static SHOWN: SyncCell<Option<ratatui::layout::Rect>> = SyncCell::new(None);
pub fn init() { pub fn init() {
// Tmux support // Tmux support
@ -38,9 +38,6 @@ pub fn init() {
// WSL support // WSL support
WSL.init(in_wsl()); WSL.init(in_wsl());
// Image state
SHOWN.with(<_>::default);
ADAPTOR.init(Adapter::matches()); ADAPTOR.init(Adapter::matches());
ADAPTOR.start(); ADAPTOR.start();
} }

View file

@ -14,7 +14,6 @@ yazi-shared = { path = "../yazi-shared", version = "0.3.3" }
# External dependencies # External dependencies
anyhow = { workspace = true } anyhow = { workspace = true }
arc-swap = { workspace = true }
bitflags = { workspace = true } bitflags = { workspace = true }
crossterm = { workspace = true } crossterm = { workspace = true }
globset = { workspace = true } globset = { workspace = true }

View file

@ -2,6 +2,13 @@ use ratatui::layout::Rect;
#[derive(Clone, Copy, Default, PartialEq, Eq)] #[derive(Clone, Copy, Default, PartialEq, Eq)]
pub struct Layout { pub struct Layout {
pub current: Rect, pub current: Rect,
pub preview: Rect, pub preview: Rect,
pub progress: Rect,
}
impl Layout {
pub const fn default() -> Self {
Self { current: Rect::ZERO, preview: Rect::ZERO, progress: Rect::ZERO }
}
} }

View file

@ -6,9 +6,7 @@ yazi_macro::mod_flat!(layout pattern preset priority);
use std::str::FromStr; use std::str::FromStr;
use yazi_shared::{RoCell, Xdg}; use yazi_shared::{RoCell, SyncCell, Xdg};
pub static LAYOUT: RoCell<arc_swap::ArcSwap<Layout>> = RoCell::new();
pub static KEYMAP: RoCell<keymap::Keymap> = RoCell::new(); pub static KEYMAP: RoCell<keymap::Keymap> = RoCell::new();
pub static LOG: RoCell<log::Log> = RoCell::new(); pub static LOG: RoCell<log::Log> = RoCell::new();
@ -23,6 +21,8 @@ pub static CONFIRM: RoCell<popup::Confirm> = RoCell::new();
pub static PICK: RoCell<popup::Pick> = RoCell::new(); pub static PICK: RoCell<popup::Pick> = RoCell::new();
pub static WHICH: RoCell<which::Which> = RoCell::new(); pub static WHICH: RoCell<which::Which> = RoCell::new();
pub static LAYOUT: SyncCell<Layout> = SyncCell::new(Layout::default());
pub fn init() -> anyhow::Result<()> { pub fn init() -> anyhow::Result<()> {
if let Err(e) = try_init(true) { if let Err(e) = try_init(true) {
eprintln!("{e}"); eprintln!("{e}");
@ -101,8 +101,6 @@ fn try_init(merge: bool) -> anyhow::Result<()> {
let pick = <_>::from_str(&yazi_toml)?; let pick = <_>::from_str(&yazi_toml)?;
let which = <_>::from_str(&yazi_toml)?; let which = <_>::from_str(&yazi_toml)?;
LAYOUT.with(<_>::default);
KEYMAP.init(keymap); KEYMAP.init(keymap);
LOG.init(log); LOG.init(log);
MANAGER.init(manager); MANAGER.init(manager);

View file

@ -69,7 +69,7 @@ impl Tab {
pub fn hovered_rect(&self) -> Option<Rect> { pub fn hovered_rect(&self) -> Option<Rect> {
let y = self.current.files.position(self.hovered()?.urn())? - self.current.offset; let y = self.current.files.position(self.hovered()?.urn())? - self.current.offset;
let mut rect = LAYOUT.load().current; let mut rect = LAYOUT.get().current;
rect.y = rect.y.saturating_sub(1) + y as u16; rect.y = rect.y.saturating_sub(1) + y as u16;
rect.height = 1; rect.height = 1;
Some(rect) Some(rect)

View file

@ -1,7 +1,7 @@
use serde::Serialize; use serde::Serialize;
use yazi_scheduler::Ongoing; use yazi_scheduler::Ongoing;
#[derive(Clone, Copy, Default, Eq, PartialEq, Serialize)] #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)]
pub struct TasksProgress { pub struct TasksProgress {
pub total: u32, pub total: u32,
pub succ: u32, pub succ: u32,

View file

@ -1,5 +1,3 @@
use std::sync::Arc;
use mlua::Value; use mlua::Value;
use ratatui::layout::Position; use ratatui::layout::Position;
use tracing::error; use tracing::error;
@ -23,7 +21,7 @@ impl App {
#[yazi_codegen::command] #[yazi_codegen::command]
pub fn reflow(&mut self, _: Opt) { pub fn reflow(&mut self, _: Opt) {
let Some(size) = self.term.as_ref().and_then(|t| t.size().ok()) else { return }; let Some(size) = self.term.as_ref().and_then(|t| t.size().ok()) else { return };
let mut layout = *LAYOUT.load_full(); let mut layout = LAYOUT.get();
let result = Lives::scope(&self.cx, |_| { let result = Lives::scope(&self.cx, |_| {
let comps = Root::reflow((Position::ORIGIN, size).into())?; let comps = Root::reflow((Position::ORIGIN, size).into())?;
@ -38,14 +36,15 @@ impl App {
match id.to_str()? { match id.to_str()? {
"current" => layout.current = *t.raw_get::<_, yazi_plugin::elements::Rect>("_area")?, "current" => layout.current = *t.raw_get::<_, yazi_plugin::elements::Rect>("_area")?,
"preview" => layout.preview = *t.raw_get::<_, yazi_plugin::elements::Rect>("_area")?, "preview" => layout.preview = *t.raw_get::<_, yazi_plugin::elements::Rect>("_area")?,
"progress" => layout.progress = *t.raw_get::<_, yazi_plugin::elements::Rect>("_area")?,
_ => {} _ => {}
} }
} }
Ok(()) Ok(())
}); });
if layout != *LAYOUT.load_full() { if layout != LAYOUT.get() {
LAYOUT.store(Arc::new(layout)); LAYOUT.set(layout);
render!(); render!();
} }

View file

@ -31,7 +31,7 @@ impl App {
Self::patch(frame, self.cx.cursor()); Self::patch(frame, self.cx.cursor());
} }
if !self.cx.notify.messages.is_empty() { if !self.cx.notify.messages.is_empty() {
self.render_notify(); self.render_partially();
} }
// Reload preview if collision is resolved // Reload preview if collision is resolved
@ -40,18 +40,19 @@ impl App {
} }
} }
pub(crate) fn render_notify(&mut self) { pub(crate) fn render_partially(&mut self) {
let Some(term) = &mut self.term else { let Some(term) = &mut self.term else { return };
return;
};
if !term.can_partial() { if !term.can_partial() {
return self.render(); return self.render();
} }
let frame = term let frame = term
.draw_partial(|f| { .draw_partial(|f| {
f.render_widget(crate::notify::Layout::new(&self.cx), f.area()); _ = Lives::scope(&self.cx, |_| {
f.render_widget(crate::tasks::Progress, f.area());
f.render_widget(crate::notify::Notify::new(&self.cx), f.area());
Ok(())
});
if let Some(pos) = self.cx.cursor() { if let Some(pos) = self.cx.cursor() {
f.set_cursor_position(pos); f.set_cursor_position(pos);

View file

@ -9,14 +9,14 @@ impl App {
pub(crate) fn update_notify(&mut self, cmd: Cmd) { pub(crate) fn update_notify(&mut self, cmd: Cmd) {
let WindowSize { rows, columns, .. } = Dimension::available(); let WindowSize { rows, columns, .. } = Dimension::available();
let area = let area =
notify::Layout::available(Rect { x: 0, y: 0, width: columns, height: rows }); notify::Notify::available(Rect { x: 0, y: 0, width: columns, height: rows });
self.cx.notify.tick(cmd, area); self.cx.notify.tick(cmd, area);
if self.cx.notify.messages.is_empty() { if self.cx.notify.messages.is_empty() {
self.render(); self.render();
} else { } else {
self.render_notify(); self.render_partially();
} }
} }
} }

View file

@ -1,9 +1,8 @@
use ratatui::backend::Backend;
use yazi_core::tasks::TasksProgress; use yazi_core::tasks::TasksProgress;
use yazi_macro::render; use yazi_macro::render;
use yazi_shared::event::Cmd; use yazi_shared::event::Cmd;
use crate::{app::App, components::Progress, lives::Lives}; use crate::app::App;
pub struct Opt { pub struct Opt {
progress: TasksProgress, progress: TasksProgress,
@ -19,41 +18,28 @@ impl TryFrom<Cmd> for Opt {
impl App { impl App {
pub(crate) fn update_progress(&mut self, opt: impl TryInto<Opt>) { pub(crate) fn update_progress(&mut self, opt: impl TryInto<Opt>) {
let Ok(opt) = opt.try_into() else { let Ok(opt) = opt.try_into() else { return };
return;
};
// Update the progress of all tasks. // Update the progress of all tasks.
let tasks = &mut self.cx.tasks; let tasks = &mut self.cx.tasks;
let progressed = tasks.progress != opt.progress;
tasks.progress = opt.progress; tasks.progress = opt.progress;
// If the task manager is visible, update the summaries with a complete render. // If the task manager is visible, update the summaries with a complete render.
if tasks.visible { if tasks.visible {
let new = tasks.paginate(); let new = tasks.paginate();
if new.len() != tasks.summaries.len() if tasks.summaries != new {
|| new.iter().zip(&tasks.summaries).any(|(a, b)| a.name != b.name)
{
tasks.summaries = new; tasks.summaries = new;
tasks.arrow(0); tasks.arrow(0);
return render!(); return render!();
} }
} }
// Otherwise, only partially update the progress. if !progressed {
let Some(term) = &mut self.term else { } else if tasks.progress.total == 0 {
return; render!();
}; } else {
self.render_partially();
_ = Lives::scope(&self.cx, |_| { }
for patch in Progress::partial_render(term.current_buffer_mut()) {
term.backend_mut().draw(patch.iter().map(|(x, y, cell)| (*x, *y, cell)))?;
if let Some(pos) = self.cx.cursor() {
term.show_cursor()?;
term.set_cursor_position(pos)?;
}
term.backend_mut().flush()?;
}
Ok(())
});
} }
} }

View file

@ -1,3 +0,0 @@
#![allow(clippy::module_inception)]
yazi_macro::mod_flat!(preview progress);

View file

@ -1,41 +0,0 @@
use std::mem;
use mlua::{AnyUserData, Table, TableExt};
use tracing::error;
use yazi_plugin::{LUA, cast_to_renderable};
pub(crate) struct Progress;
impl Progress {
pub(crate) fn partial_render(
buf: &mut ratatui::buffer::Buffer,
) -> Vec<Vec<(u16, u16, ratatui::buffer::Cell)>> {
let mut patches = vec![];
let mut f = || {
let comp: Table = LUA.globals().raw_get("Progress")?;
for widget in comp.call_method::<_, Vec<AnyUserData>>("partial_redraw", ())? {
let Some(w) = cast_to_renderable(&widget) else { continue };
let area = w.area();
w.render(buf);
let mut patch = Vec::with_capacity(area.width as usize * area.height as usize);
for y in area.top()..area.bottom() {
for x in area.left()..area.right() {
patch.push((x, y, mem::take(&mut buf[(x, y)])));
}
}
buf.reset();
patches.push(patch);
}
Ok::<_, anyhow::Error>(())
};
if let Err(e) = f() {
error!("{e}");
}
patches
}
}

View file

@ -4,11 +4,11 @@ use yazi_config::{KEYMAP, THEME};
use super::Bindings; use super::Bindings;
use crate::Ctx; use crate::Ctx;
pub(crate) struct Layout<'a> { pub(crate) struct Help<'a> {
cx: &'a Ctx, cx: &'a Ctx,
} }
impl<'a> Layout<'a> { impl<'a> Help<'a> {
pub fn new(cx: &'a Ctx) -> Self { Self { cx } } pub fn new(cx: &'a Ctx) -> Self { Self { cx } }
fn tips() -> String { fn tips() -> String {
@ -19,7 +19,7 @@ impl<'a> Layout<'a> {
} }
} }
impl<'a> Widget for Layout<'a> { impl<'a> Widget for Help<'a> {
fn render(self, area: Rect, buf: &mut Buffer) { fn render(self, area: Rect, buf: &mut Buffer) {
let help = &self.cx.help; let help = &self.cx.help;
yazi_plugin::elements::Clear::default().render(area, buf); yazi_plugin::elements::Clear::default().render(area, buf);

View file

@ -1 +1 @@
yazi_macro::mod_flat!(bindings layout); yazi_macro::mod_flat!(bindings help);

View file

@ -28,7 +28,7 @@ impl Folder {
let window = match window { let window = match window {
Some(w) => w, Some(w) => w,
None => { None => {
let limit = LAYOUT.load().preview.height as usize; let limit = LAYOUT.get().preview.height as usize;
inner.offset..inner.files.len().min(inner.offset + limit) inner.offset..inner.files.len().min(inner.offset + limit)
} }
}; };

View file

@ -28,7 +28,7 @@ impl Preview {
me.tab() me.tab()
.hovered_folder() .hovered_folder()
.map(|f| { .map(|f| {
let limit = LAYOUT.load().preview.height as usize; let limit = LAYOUT.get().preview.height as usize;
Folder::make(Some(me.skip..f.files.len().min(me.skip + limit)), f, me.tab()) Folder::make(Some(me.skip..f.files.len().min(me.skip + limit)), f, me.tab())
}) })
.transpose() .transpose()

View file

@ -5,9 +5,7 @@
#[global_allocator] #[global_allocator]
static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
yazi_macro::mod_pub!( yazi_macro::mod_pub!(app completion confirm help input lives manager notify pick tasks which);
app completion components confirm help input lives notify pick tasks which
);
yazi_macro::mod_flat!(context executor logs panic root router signals term); yazi_macro::mod_flat!(context executor logs panic root router signals term);

View file

@ -0,0 +1 @@
yazi_macro::mod_flat!(preview);

View file

@ -1 +1 @@
yazi_macro::mod_flat!(layout); yazi_macro::mod_flat!(notify);

View file

@ -3,11 +3,11 @@ use yazi_core::notify::Message;
use crate::Ctx; use crate::Ctx;
pub(crate) struct Layout<'a> { pub(crate) struct Notify<'a> {
cx: &'a Ctx, cx: &'a Ctx,
} }
impl<'a> Layout<'a> { impl<'a> Notify<'a> {
pub(crate) fn new(cx: &'a Ctx) -> Self { Self { cx } } pub(crate) fn new(cx: &'a Ctx) -> Self { Self { cx } }
pub(crate) fn available(area: Rect) -> Rect { pub(crate) fn available(area: Rect) -> Rect {
@ -38,7 +38,7 @@ impl<'a> Layout<'a> {
} }
} }
impl<'a> Widget for Layout<'a> { impl<'a> Widget for Notify<'a> {
fn render(self, area: Rect, buf: &mut Buffer) { fn render(self, area: Rect, buf: &mut Buffer) {
let notify = &self.cx.notify; let notify = &self.cx.notify;

View file

@ -3,8 +3,8 @@ use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget};
use tracing::error; use tracing::error;
use yazi_plugin::{LUA, elements::render_widgets}; use yazi_plugin::{LUA, elements::render_widgets};
use super::{completion, confirm, input, pick, tasks, which}; use super::{completion, confirm, help, input, manager, pick, tasks, which};
use crate::{Ctx, components, help}; use crate::Ctx;
pub(super) struct Root<'a> { pub(super) struct Root<'a> {
cx: &'a Ctx, cx: &'a Ctx,
@ -33,10 +33,10 @@ impl<'a> Widget for Root<'a> {
error!("Failed to redraw the `Root` component:\n{e}"); error!("Failed to redraw the `Root` component:\n{e}");
} }
components::Preview::new(self.cx).render(area, buf); manager::Preview::new(self.cx).render(area, buf);
if self.cx.tasks.visible { if self.cx.tasks.visible {
tasks::Layout::new(self.cx).render(area, buf); tasks::Tasks::new(self.cx).render(area, buf);
} }
if self.cx.pick.visible { if self.cx.pick.visible {
@ -52,7 +52,7 @@ impl<'a> Widget for Root<'a> {
} }
if self.cx.help.visible { if self.cx.help.visible {
help::Layout::new(self.cx).render(area, buf); help::Help::new(self.cx).render(area, buf);
} }
if self.cx.completion.visible { if self.cx.completion.visible {

View file

@ -1 +1 @@
yazi_macro::mod_flat!(layout); yazi_macro::mod_flat!(progress tasks);

View file

@ -0,0 +1,23 @@
use mlua::{Table, TableExt};
use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget};
use tracing::error;
use yazi_config::LAYOUT;
use yazi_plugin::{LUA, elements::render_widgets};
pub(crate) struct Progress;
impl Widget for Progress {
fn render(self, _: Rect, buf: &mut Buffer) {
let mut f = || {
let area = yazi_plugin::elements::Rect::from(LAYOUT.get().progress);
let progress =
LUA.globals().raw_get::<_, Table>("Progress")?.call_method::<_, Table>("use", area)?;
render_widgets(progress.call_method("redraw", ())?, buf);
Ok::<_, mlua::Error>(())
};
if let Err(e) = f() {
error!("Failed to redraw the `Progress` component:\n{e}");
}
}
}

View file

@ -4,11 +4,11 @@ use yazi_core::tasks::TASKS_PERCENT;
use crate::Ctx; use crate::Ctx;
pub(crate) struct Layout<'a> { pub(crate) struct Tasks<'a> {
cx: &'a Ctx, cx: &'a Ctx,
} }
impl<'a> Layout<'a> { impl<'a> Tasks<'a> {
pub(crate) fn new(cx: &'a Ctx) -> Self { Self { cx } } pub(crate) fn new(cx: &'a Ctx) -> Self { Self { cx } }
pub(super) fn area(area: Rect) -> Rect { pub(super) fn area(area: Rect) -> Rect {
@ -28,7 +28,7 @@ impl<'a> Layout<'a> {
} }
} }
impl<'a> Widget for Layout<'a> { impl<'a> Widget for Tasks<'a> {
fn render(self, area: Rect, buf: &mut Buffer) { fn render(self, area: Rect, buf: &mut Buffer) {
let area = Self::area(area); let area = Self::area(area);

View file

@ -1 +1 @@
yazi_macro::mod_flat!(cand layout); yazi_macro::mod_flat!(cand which);

View file

@ -94,7 +94,7 @@ impl Folder {
} }
pub fn sync_page(&mut self, force: bool) { pub fn sync_page(&mut self, force: bool) {
let limit = LAYOUT.load().current.height as usize; let limit = LAYOUT.get().current.height as usize;
if limit == 0 { if limit == 0 {
return; return;
} }
@ -109,7 +109,7 @@ impl Folder {
let old = (self.cursor, self.offset); let old = (self.cursor, self.offset);
let len = self.files.len(); let len = self.files.len();
let limit = LAYOUT.load().current.height as usize; let limit = LAYOUT.get().current.height as usize;
let scrolloff = (limit / 2).min(MANAGER.scrolloff as usize); let scrolloff = (limit / 2).min(MANAGER.scrolloff as usize);
self.cursor = step.add(self.cursor, limit).min(len.saturating_sub(1)); self.cursor = step.add(self.cursor, limit).min(len.saturating_sub(1));
@ -126,7 +126,7 @@ impl Folder {
let old = (self.cursor, self.offset); let old = (self.cursor, self.offset);
let max = self.files.len().saturating_sub(1); let max = self.files.len().saturating_sub(1);
let limit = LAYOUT.load().current.height as usize; let limit = LAYOUT.get().current.height as usize;
let scrolloff = (limit / 2).min(MANAGER.scrolloff as usize); let scrolloff = (limit / 2).min(MANAGER.scrolloff as usize);
self.cursor = step.add(self.cursor, limit).min(max); self.cursor = step.add(self.cursor, limit).min(max);
@ -143,7 +143,7 @@ impl Folder {
let old = self.offset; let old = self.offset;
let len = self.files.len(); let len = self.files.len();
let limit = LAYOUT.load().current.height as usize; let limit = LAYOUT.get().current.height as usize;
let scrolloff = (limit / 2).min(MANAGER.scrolloff as usize); let scrolloff = (limit / 2).min(MANAGER.scrolloff as usize);
self.offset = if self.cursor < (self.offset + limit).min(len).saturating_sub(scrolloff) { self.offset = if self.cursor < (self.offset + limit).min(len).saturating_sub(scrolloff) {
@ -162,7 +162,7 @@ impl Folder {
pub fn paginate(&self, page: usize) -> &[File] { pub fn paginate(&self, page: usize) -> &[File] {
let len = self.files.len(); let len = self.files.len();
let limit = LAYOUT.load().current.height as usize; let limit = LAYOUT.get().current.height as usize;
let start = (page.saturating_sub(1) * limit).min(len.saturating_sub(1)); let start = (page.saturating_sub(1) * limit).min(len.saturating_sub(1));
let end = ((page + 2) * limit).min(len); let end = ((page + 2) * limit).min(len);

View file

@ -1,24 +1,30 @@
Progress = { Progress = {
_area = ui.Rect.default, -- TODO: remove this _id = "progress",
} }
function Progress:redraw(area, offset) function Progress:new(area, offset)
self._area = ui.Rect { local me = setmetatable({ _area = area, _offset = offset }, { __index = self })
x = math.max(0, area.w - offset - 21), me:layout()
y = area.y, return me
w = ya.clamp(0, area.w - offset - 1, 20),
h = math.min(1, area.h),
}
return self:partial_redraw()
end end
-- Progress bars usually need frequent updates to report the latest task progress. function Progress:use(area) return setmetatable({ _area = area }, { __index = self }) end
-- We use `partial_redraw()` to partially redraw it when there is progress change,
-- which has almost no cost compared to a full redraw by `redraw()`. function Progress:layout()
function Progress:partial_redraw() self._area = ui.Rect {
x = math.max(0, self._area.w - self._offset - 21),
y = self._area.y,
w = ya.clamp(0, self._area.w - self._offset - 1, 20),
h = math.min(1, self._area.h),
}
end
function Progress:reflow() return { self } end
function Progress:redraw()
local progress = cx.tasks.progress local progress = cx.tasks.progress
if progress.total == 0 then if progress.total == 0 then
return { ui.Text {} } return {}
end end
local gauge = ui.Gauge():area(self._area) local gauge = ui.Gauge():area(self._area)

View file

@ -10,8 +10,8 @@ Status = {
{ "name", id = 3, order = 3000 }, { "name", id = 3, order = 3000 },
}, },
_right = { _right = {
{ "permissions", id = 4, order = 1000 }, { "perm", id = 4, order = 1000 },
{ "percentage", id = 5, order = 2000 }, { "percent", id = 5, order = 2000 },
{ "position", id = 6, order = 3000 }, { "position", id = 6, order = 3000 },
}, },
} }
@ -67,7 +67,7 @@ function Status:name()
return ui.Line(" " .. h.name) return ui.Line(" " .. h.name)
end end
function Status:permissions() function Status:perm()
local h = self._current.hovered local h = self._current.hovered
if not h then if not h then
return ui.Line {} return ui.Line {}
@ -96,7 +96,7 @@ function Status:permissions()
return ui.Line(spans) return ui.Line(spans)
end end
function Status:percentage() function Status:percent()
local percent = 0 local percent = 0
local cursor = self._current.cursor local cursor = self._current.cursor
local length = #self._current.files local length = #self._current.files
@ -142,7 +142,7 @@ function Status:redraw()
return { return {
ui.Text(left):area(self._area), ui.Text(left):area(self._area),
ui.Text(right):area(self._area):align(ui.Text.RIGHT), ui.Text(right):area(self._area):align(ui.Text.RIGHT),
table.unpack(Progress:redraw(self._area, right_width)), table.unpack(ya.redraw_with(Progress:new(self._area, right_width))),
} }
end end

View file

@ -22,8 +22,8 @@ function M:seek(units)
end end
function M:preload() function M:preload()
local percentage = 5 + self.skip local percent = 5 + self.skip
if percentage > 95 then if percent > 95 then
ya.manager_emit("peek", { 90, only_if = self.file.url, upper_bound = true }) ya.manager_emit("peek", { 90, only_if = self.file.url, upper_bound = true })
return 2 return 2
end end
@ -48,7 +48,7 @@ function M:preload()
"-o", "-o",
tostring(cache), tostring(cache),
"-t", "-t",
tostring(percentage), tostring(percent),
"-s", "-s",
tostring(PREVIEW.max_width), tostring(PREVIEW.max_width),
}):spawn() }):spawn()

View file

@ -26,7 +26,7 @@ pub async fn fetch(name: &str, files: Vec<yazi_shared::fs::File>) -> mlua::Resul
} }
plugin.raw_set("skip", 0)?; plugin.raw_set("skip", 0)?;
plugin.raw_set("area", Rect::from(LAYOUT.load().preview))?; plugin.raw_set("area", Rect::from(LAYOUT.get().preview))?;
plugin.raw_set("files", files)?; plugin.raw_set("files", files)?;
Handle::current().block_on(plugin.call_async_method("fetch", ())) Handle::current().block_on(plugin.call_async_method("fetch", ()))

View file

@ -41,7 +41,7 @@ pub fn peek(
plugin.raw_set("file", File::cast(&lua, file)?)?; plugin.raw_set("file", File::cast(&lua, file)?)?;
plugin.raw_set("_mime", mime)?; plugin.raw_set("_mime", mime)?;
plugin.raw_set("skip", skip)?; plugin.raw_set("skip", skip)?;
plugin.raw_set("area", Rect::from(LAYOUT.load().preview))?; plugin.raw_set("area", Rect::from(LAYOUT.get().preview))?;
plugin.raw_set("window", Window::default())?; plugin.raw_set("window", Window::default())?;
if ct2.is_cancelled() { Ok(()) } else { plugin.call_async_method("peek", ()).await } if ct2.is_cancelled() { Ok(()) } else { plugin.call_async_method("peek", ()).await }
@ -69,7 +69,7 @@ pub fn peek_sync(cmd: &Cmd, file: yazi_shared::fs::File, mime: Cow<'static, str>
plugin.raw_set("file", File::cast(&LUA, file)?)?; plugin.raw_set("file", File::cast(&LUA, file)?)?;
plugin.raw_set("_mime", mime)?; plugin.raw_set("_mime", mime)?;
plugin.raw_set("skip", skip)?; plugin.raw_set("skip", skip)?;
plugin.raw_set("area", Rect::from(LAYOUT.load().preview))?; plugin.raw_set("area", Rect::from(LAYOUT.get().preview))?;
plugin.raw_set("window", Window::default())?; plugin.raw_set("window", Window::default())?;
plugin.call_method("peek", ()) plugin.call_method("peek", ())
}); });

View file

@ -18,7 +18,7 @@ pub async fn preload(name: &str, file: yazi_shared::fs::File) -> mlua::Result<u8
}; };
plugin.raw_set("skip", 0)?; plugin.raw_set("skip", 0)?;
plugin.raw_set("area", Rect::from(LAYOUT.load().preview))?; plugin.raw_set("area", Rect::from(LAYOUT.get().preview))?;
plugin.raw_set("file", File::cast(&lua, file)?)?; plugin.raw_set("file", File::cast(&lua, file)?)?;
Handle::current().block_on(plugin.call_async_method("preload", ())) Handle::current().block_on(plugin.call_async_method("preload", ()))

View file

@ -8,7 +8,7 @@ use crate::{LUA, Opt, OptCallback, bindings::Cast, elements::Rect, file::File};
pub fn seek_sync(cmd: &Cmd, file: yazi_shared::fs::File, units: i16) { pub fn seek_sync(cmd: &Cmd, file: yazi_shared::fs::File, units: i16) {
let cb: OptCallback = Box::new(move |_, plugin| { let cb: OptCallback = Box::new(move |_, plugin| {
plugin.raw_set("file", File::cast(&LUA, file)?)?; plugin.raw_set("file", File::cast(&LUA, file)?)?;
plugin.raw_set("area", Rect::from(LAYOUT.load().preview))?; plugin.raw_set("area", Rect::from(LAYOUT.get().preview))?;
plugin.call_method("seek", units) plugin.call_method("seek", units)
}); });

View file

@ -1,4 +1,4 @@
use std::{collections::HashMap, sync::Arc}; use std::collections::HashMap;
use mlua::{ExternalError, Lua, Table, TableExt, Value}; use mlua::{ExternalError, Lua, Table, TableExt, Value};
use tracing::error; use tracing::error;
@ -42,22 +42,15 @@ impl Utils {
let id: mlua::String = c.get("_id")?; let id: mlua::String = c.get("_id")?;
let id = id.to_str()?; let id = id.to_str()?;
let mut layout = LAYOUT.get();
match id { match id {
"current" => { "current" => layout.current = *c.raw_get::<_, crate::elements::Rect>("_area")?,
LAYOUT.store(Arc::new(yazi_config::Layout { "preview" => layout.preview = *c.raw_get::<_, crate::elements::Rect>("_area")?,
current: *c.raw_get::<_, crate::elements::Rect>("_area")?, "progress" => layout.progress = *c.raw_get::<_, crate::elements::Rect>("_area")?,
..*LAYOUT.load_full()
}));
}
"preview" => {
LAYOUT.store(Arc::new(yazi_config::Layout {
preview: *c.raw_get::<_, crate::elements::Rect>("_area")?,
..*LAYOUT.load_full()
}));
}
_ => {} _ => {}
} }
LAYOUT.set(layout);
match c.call_method::<_, Table>("redraw", ()) { match c.call_method::<_, Table>("redraw", ()) {
Err(e) => { Err(e) => {
error!("Failed to `redraw()` the `{id}` component:\n{e}"); error!("Failed to `redraw()` the `{id}` component:\n{e}");

View file

@ -31,7 +31,7 @@ pub enum TaskKind {
Preload, Preload,
} }
#[derive(Debug)] #[derive(Debug, PartialEq, Eq)]
pub struct TaskSummary { pub struct TaskSummary {
pub name: String, pub name: String,

View file

@ -1,17 +1,17 @@
use std::{cell::Cell, ffi::OsStr, fs::{FileType, Metadata}, ops::Deref}; use std::{ffi::OsStr, fs::{FileType, Metadata}, ops::Deref};
use anyhow::Result; use anyhow::Result;
use tokio::fs; use tokio::fs;
use super::{Urn, UrnBuf}; use super::{Urn, UrnBuf};
use crate::{fs::{Cha, Url}, theme::IconCache}; use crate::{SyncCell, fs::{Cha, Url}, theme::IconCache};
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct File { pub struct File {
pub url: Url, pub url: Url,
pub cha: Cha, pub cha: Cha,
pub link_to: Option<Url>, pub link_to: Option<Url>,
pub icon: Cell<IconCache>, pub icon: SyncCell<IconCache>,
} }
impl Deref for File { impl Deref for File {

View file

@ -2,7 +2,7 @@
yazi_macro::mod_pub!(errors event fs shell theme translit); yazi_macro::mod_pub!(errors event fs shell theme translit);
yazi_macro::mod_flat!(chars condition debounce env id layer natsort number os rand ro_cell terminal throttle time xdg); yazi_macro::mod_flat!(chars condition debounce env id layer natsort number os rand ro_cell sync_cell terminal throttle time xdg);
pub fn init() { pub fn init() {
#[cfg(unix)] #[cfg(unix)]

View file

@ -27,12 +27,6 @@ impl<T> RoCell<T> {
self.init(f()); self.init(f());
} }
#[inline]
pub fn replace(&self, value: T) -> T {
debug_assert!(self.initialized());
unsafe { mem::replace(&mut *self.0.get(), Some(value)).unwrap_unchecked() }
}
#[inline] #[inline]
pub fn drop(&self) -> T { pub fn drop(&self) -> T {
debug_assert!(self.initialized()); debug_assert!(self.initialized());

View file

@ -0,0 +1,36 @@
use std::{cell::Cell, fmt::{Debug, Formatter}, ops::Deref};
/// [`SyncCell`], but [`Sync`].
///
/// This is just an `Cell`, except it implements `Sync`
/// if `T` implements `Sync`.
pub struct SyncCell<T: ?Sized>(Cell<T>);
unsafe impl<T: ?Sized + Sync> Sync for SyncCell<T> {}
impl<T> SyncCell<T> {
#[inline]
pub const fn new(value: T) -> Self { Self(Cell::new(value)) }
}
impl<T: Default> Default for SyncCell<T> {
fn default() -> Self { Self::new(T::default()) }
}
impl<T> Deref for SyncCell<T> {
type Target = Cell<T>;
#[inline]
fn deref(&self) -> &Self::Target { &self.0 }
}
impl<T: Copy> Clone for SyncCell<T> {
#[inline]
fn clone(&self) -> SyncCell<T> { SyncCell::new(self.get()) }
}
impl<T: Copy + Debug> Debug for SyncCell<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SyncCell").field("value", &self.get()).finish()
}
}