perf: reuse previewed and spotted widgets when possible (#3765)

This commit is contained in:
三咲雅 misaki masa 2026-03-15 15:18:58 +08:00 committed by GitHub
parent fa1ee46edc
commit d22c96b69c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 197 additions and 55 deletions

View file

@ -55,6 +55,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
### Improved
- Reduce memory allocations by using Lua 5.5 external strings ([#3634])
- Reuse previewed and spotted widgets when possible ([#3765])
## [v26.1.22]
@ -1682,3 +1683,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
[#3744]: https://github.com/sxyazi/yazi/pull/3744
[#3748]: https://github.com/sxyazi/yazi/pull/3748
[#3757]: https://github.com/sxyazi/yazi/pull/3757
[#3765]: https://github.com/sxyazi/yazi/pull/3765

View file

@ -1,5 +1,5 @@
use mlua::{AnyUserData, IntoLua, Lua, MetaMethod, Table, UserData, Value};
use ratatui::widgets::Borders;
use ratatui::widgets::{Borders, Widget};
use super::{Area, Edge};
@ -21,8 +21,22 @@ impl Bar {
bar.set_metatable(Some(lua.create_table_from([(MetaMethod::Call.name(), new)])?))?;
bar.into_lua(lua)
}
}
pub(super) fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer) {
impl Widget for Bar {
fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)
where
Self: Sized,
{
(&self).render(rect, buf);
}
}
impl Widget for &Bar {
fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)
where
Self: Sized,
{
if rect.area() == 0 {
return;
}

View file

@ -42,8 +42,13 @@ impl Border {
border.set_metatable(Some(lua.create_table_from([(MetaMethod::Call.name(), new)])?))?;
border.into_lua(lua)
}
}
pub(super) fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer) {
impl Widget for Border {
fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)
where
Self: Sized,
{
let mut block = ratatui::widgets::Block::default()
.borders(self.edge.0)
.border_type(self.r#type)
@ -51,8 +56,8 @@ impl Border {
for title in self.titles {
block = match title {
(ratatui::widgets::TitlePosition::Top, line) => block.title(line),
(ratatui::widgets::TitlePosition::Bottom, line) => block.title(line),
(ratatui::widgets::TitlePosition::Top, line) => block.title_top(line),
(ratatui::widgets::TitlePosition::Bottom, line) => block.title_bottom(line),
};
}
@ -60,6 +65,15 @@ impl Border {
}
}
impl Widget for &Border {
fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)
where
Self: Sized,
{
self.clone().render(rect, buf);
}
}
impl UserData for Border {
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
crate::impl_area_method!(methods);

View file

@ -17,8 +17,22 @@ impl Clear {
clear.into_lua(lua)
}
}
pub(super) fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer) {
impl Widget for Clear {
fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)
where
Self: Sized,
{
(&self).render(rect, buf);
}
}
impl Widget for &Clear {
fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)
where
Self: Sized,
{
yazi_widgets::Clear.render(rect, buf);
}
}

View file

@ -23,8 +23,13 @@ impl Gauge {
gauge.into_lua(lua)
}
}
pub(super) fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer) {
impl Widget for Gauge {
fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)
where
Self: Sized,
{
let mut gauge = ratatui::widgets::Gauge::default()
.ratio(self.ratio)
.style(self.style)
@ -38,6 +43,15 @@ impl Gauge {
}
}
impl Widget for &Gauge {
fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)
where
Self: Sized,
{
self.clone().render(rect, buf);
}
}
impl UserData for Gauge {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
crate::impl_area_method!(methods);

View file

@ -49,10 +49,6 @@ impl Line {
line.set_metatable(Some(lua.create_table_from([(MetaMethod::Call.name(), new)])?))?;
line.into_lua(lua)
}
pub(super) fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer) {
self.inner.render(rect, buf);
}
}
impl TryFrom<Table> for Line {
@ -84,6 +80,24 @@ impl From<Line> for ratatui::text::Line<'static> {
fn from(value: Line) -> Self { value.inner }
}
impl Widget for Line {
fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)
where
Self: Sized,
{
(&self).render(rect, buf);
}
}
impl Widget for &Line {
fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)
where
Self: Sized,
{
(&self.inner).render(rect, buf);
}
}
impl FromLua for Line {
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
Ok(Self {

View file

@ -22,9 +22,23 @@ impl List {
list.into_lua(lua)
}
}
pub(super) fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer) {
self.inner.render(rect, buf);
impl Widget for List {
fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)
where
Self: Sized,
{
(&self).render(rect, buf);
}
}
impl Widget for &List {
fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)
where
Self: Sized,
{
(&self.inner).render(rect, buf);
}
}

View file

@ -1,6 +1,7 @@
use std::any::TypeId;
use mlua::{AnyUserData, ExternalError, FromLua, Lua, Value};
use ratatui::widgets::Widget;
use super::{Bar, Border, Clear, Gauge, Line, List, Table, Text};
use crate::{Error, elements::Area};
@ -46,19 +47,6 @@ impl Renderable {
self
}
pub fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer) {
match self {
Self::Line(line) => line.render(rect, buf),
Self::Text(text) => text.render(rect, buf),
Self::List(list) => list.render(rect, buf),
Self::Bar(bar) => bar.render(rect, buf),
Self::Clear(clear) => clear.render(rect, buf),
Self::Border(border) => border.render(rect, buf),
Self::Gauge(gauge) => gauge.render(rect, buf),
Self::Table(table) => table.render(rect, buf),
}
}
pub fn render_with<T>(self, buf: &mut ratatui::buffer::Buffer, trans: T)
where
T: FnOnce(yazi_config::popup::Position) -> ratatui::layout::Rect,
@ -96,6 +84,42 @@ impl From<Error> for Renderable {
}
}
impl Widget for Renderable {
fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)
where
Self: Sized,
{
match self {
Self::Line(line) => line.render(rect, buf),
Self::Text(text) => text.render(rect, buf),
Self::List(list) => list.render(rect, buf),
Self::Bar(bar) => bar.render(rect, buf),
Self::Clear(clear) => clear.render(rect, buf),
Self::Border(border) => border.render(rect, buf),
Self::Gauge(gauge) => gauge.render(rect, buf),
Self::Table(table) => table.render(rect, buf),
}
}
}
impl Widget for &Renderable {
fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)
where
Self: Sized,
{
match self {
Renderable::Line(line) => line.render(rect, buf),
Renderable::Text(text) => text.render(rect, buf),
Renderable::List(list) => (&**list).render(rect, buf),
Renderable::Bar(bar) => bar.render(rect, buf),
Renderable::Clear(clear) => clear.render(rect, buf),
Renderable::Border(border) => border.render(rect, buf),
Renderable::Gauge(gauge) => (&**gauge).render(rect, buf),
Renderable::Table(table) => (&**table).render(rect, buf),
}
}
}
impl FromLua for Renderable {
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
match value {

View file

@ -1,5 +1,5 @@
use mlua::{AnyUserData, IntoLua, Lua, MetaMethod, UserData, Value};
use ratatui::widgets::StatefulWidget;
use ratatui::widgets::{StatefulWidget, Widget};
use super::{Area, Row};
use crate::{Style, elements::Constraint};
@ -47,7 +47,30 @@ impl Table {
if row.cells.is_empty() { None } else { Some(&row.cells[col.min(row.cells.len() - 1)].text) }
}
pub(super) fn render(mut self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer) {
pub fn len(&self) -> usize { self.rows.len() }
pub fn select(&mut self, idx: Option<usize>) {
self
.state
.select(idx.map(|i| if self.rows.is_empty() { 0 } else { i.min(self.rows.len() - 1) }));
}
pub fn selected(&self) -> Option<usize> {
if self.rows.is_empty() { None } else { Some(self.state.selected()?.min(self.rows.len() - 1)) }
}
}
impl TryFrom<AnyUserData> for Table {
type Error = mlua::Error;
fn try_from(value: AnyUserData) -> Result<Self, Self::Error> { value.take() }
}
impl Widget for Table {
fn render(mut self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)
where
Self: Sized,
{
let mut table = ratatui::widgets::Table::new(self.rows, self.widths)
.column_spacing(self.column_spacing)
.style(self.style)
@ -68,26 +91,17 @@ impl Table {
table = table.block(block);
}
table.render(rect, buf, &mut self.state);
}
pub fn len(&self) -> usize { self.rows.len() }
pub fn select(&mut self, idx: Option<usize>) {
self
.state
.select(idx.map(|i| if self.rows.is_empty() { 0 } else { i.min(self.rows.len() - 1) }));
}
pub fn selected(&self) -> Option<usize> {
if self.rows.is_empty() { None } else { Some(self.state.selected()?.min(self.rows.len() - 1)) }
StatefulWidget::render(table, rect, buf, &mut self.state);
}
}
impl TryFrom<AnyUserData> for Table {
type Error = mlua::Error;
fn try_from(value: AnyUserData) -> Result<Self, Self::Error> { value.take() }
impl Widget for &Table {
fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)
where
Self: Sized,
{
self.clone().render(rect, buf);
}
}
impl UserData for Table {

View file

@ -31,14 +31,6 @@ impl Text {
text.set_metatable(Some(lua.create_table_from([(MetaMethod::Call.name(), new)])?))?;
text.into_lua(lua)
}
pub(super) fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer) {
if self.wrap.is_none() && self.scroll == Default::default() {
self.inner.render(rect, buf);
} else {
ratatui::widgets::Paragraph::from(self).render(rect, buf);
}
}
}
impl TryFrom<Table> for Text {
@ -82,6 +74,32 @@ impl From<Text> for ratatui::widgets::Paragraph<'static> {
}
}
impl Widget for Text {
fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)
where
Self: Sized,
{
if self.wrap.is_none() && self.scroll == Default::default() {
self.inner.render(rect, buf);
} else {
ratatui::widgets::Paragraph::from(self).render(rect, buf);
}
}
}
impl Widget for &Text {
fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)
where
Self: Sized,
{
if self.wrap.is_none() && self.scroll == Default::default() {
(&self.inner).render(rect, buf);
} else {
ratatui::widgets::Paragraph::from(self.clone()).render(rect, buf);
}
}
}
impl FromLua for Text {
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
let inner = match value {

View file

@ -24,7 +24,7 @@ impl Widget for Preview<'_> {
for w in &lock.data {
let rect = w.area().transform(|p| self.core.mgr.area(p));
if rect.intersection(win) == rect {
w.clone().render(rect, buf);
w.render(rect, buf);
}
}
}

View file

@ -18,7 +18,7 @@ impl Widget for Spot<'_> {
for w in &lock.data {
let rect = w.area().transform(|p| self.core.mgr.area(p));
if rect.intersection(win) == rect {
w.clone().render(rect, buf);
w.render(rect, buf);
}
}
}