mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
feat: cross-directory selection
This commit is contained in:
parent
6aae7683ba
commit
24243e6c60
15 changed files with 189 additions and 128 deletions
|
|
@ -37,7 +37,10 @@ impl Pattern {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_wildcard(&self) -> bool { self.inner.as_str() == "*" || self.inner.as_str() == "*/" }
|
pub fn any_file(&self) -> bool { self.inner.as_str() == "*" }
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn any_dir(&self) -> bool { self.inner.as_str() == "*/" }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&str> for Pattern {
|
impl TryFrom<&str> for Pattern {
|
||||||
|
|
|
||||||
|
|
@ -11,24 +11,6 @@ pub struct Plugin {
|
||||||
pub previewers: Vec<PluginRule>,
|
pub previewers: Vec<PluginRule>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub struct PluginRule {
|
|
||||||
#[serde(default)]
|
|
||||||
pub id: u8,
|
|
||||||
pub cond: Option<Condition>,
|
|
||||||
pub name: Option<Pattern>,
|
|
||||||
pub mime: Option<Pattern>,
|
|
||||||
#[serde(rename = "exec")]
|
|
||||||
#[serde(deserialize_with = "super::exec_deserialize")]
|
|
||||||
pub cmd: Cmd,
|
|
||||||
#[serde(default)]
|
|
||||||
pub sync: bool,
|
|
||||||
#[serde(default)]
|
|
||||||
pub multi: bool,
|
|
||||||
#[serde(default)]
|
|
||||||
pub prio: Priority,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Plugin {
|
impl Default for Plugin {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
|
@ -52,11 +34,17 @@ impl Default for Plugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut shadow = toml::from_str::<Outer>(&MERGED_YAZI).unwrap().plugin;
|
let mut shadow = toml::from_str::<Outer>(&MERGED_YAZI).unwrap().plugin;
|
||||||
if shadow.append_preloaders.iter().any(|r| r.name.as_ref().is_some_and(|p| p.is_wildcard())) {
|
if shadow.append_preloaders.iter().any(|r| r.any_file()) {
|
||||||
shadow.preloaders.retain(|r| !r.name.as_ref().is_some_and(|p| p.is_wildcard()));
|
shadow.preloaders.retain(|r| !r.any_file());
|
||||||
}
|
}
|
||||||
if shadow.append_previewers.iter().any(|r| r.name.as_ref().is_some_and(|p| p.is_wildcard())) {
|
if shadow.append_preloaders.iter().any(|r| r.any_dir()) {
|
||||||
shadow.previewers.retain(|r| !r.name.as_ref().is_some_and(|p| p.is_wildcard()));
|
shadow.preloaders.retain(|r| !r.any_dir());
|
||||||
|
}
|
||||||
|
if shadow.append_previewers.iter().any(|r| r.any_file()) {
|
||||||
|
shadow.previewers.retain(|r| !r.any_file());
|
||||||
|
}
|
||||||
|
if shadow.append_previewers.iter().any(|r| r.any_dir()) {
|
||||||
|
shadow.previewers.retain(|r| !r.any_dir());
|
||||||
}
|
}
|
||||||
|
|
||||||
Preset::mix(&mut shadow.preloaders, shadow.prepend_preloaders, shadow.append_preloaders);
|
Preset::mix(&mut shadow.preloaders, shadow.prepend_preloaders, shadow.append_preloaders);
|
||||||
|
|
@ -104,3 +92,29 @@ impl Plugin {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct PluginRule {
|
||||||
|
#[serde(default)]
|
||||||
|
pub id: u8,
|
||||||
|
pub cond: Option<Condition>,
|
||||||
|
pub name: Option<Pattern>,
|
||||||
|
pub mime: Option<Pattern>,
|
||||||
|
#[serde(rename = "exec")]
|
||||||
|
#[serde(deserialize_with = "super::exec_deserialize")]
|
||||||
|
pub cmd: Cmd,
|
||||||
|
#[serde(default)]
|
||||||
|
pub sync: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub multi: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub prio: Priority,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PluginRule {
|
||||||
|
#[inline]
|
||||||
|
fn any_file(&self) -> bool { self.name.as_ref().is_some_and(|p| p.any_file()) }
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn any_dir(&self) -> bool { self.name.as_ref().is_some_and(|p| p.any_dir()) }
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,13 @@ impl Icon {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut outer = IconOuter::deserialize(deserializer)?;
|
let mut outer = IconOuter::deserialize(deserializer)?;
|
||||||
|
if outer.append_rules.iter().any(|r| r.name.any_file()) {
|
||||||
|
outer.rules.retain(|r| !r.name.any_file());
|
||||||
|
}
|
||||||
|
if outer.append_rules.iter().any(|r| r.name.any_dir()) {
|
||||||
|
outer.rules.retain(|r| !r.name.any_dir());
|
||||||
|
}
|
||||||
|
|
||||||
Preset::mix(&mut outer.rules, outer.prepend_rules, outer.append_rules);
|
Preset::mix(&mut outer.rules, outer.prepend_rules, outer.append_rules);
|
||||||
|
|
||||||
Ok(
|
Ok(
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,18 @@ use yazi_shared::{emit, event::Cmd, Layer};
|
||||||
use crate::notify::{Message, Notify};
|
use crate::notify::{Message, Notify};
|
||||||
|
|
||||||
impl Notify {
|
impl Notify {
|
||||||
|
#[inline]
|
||||||
|
pub fn _push_warn(title: &str, content: &str) {
|
||||||
|
emit!(Call(
|
||||||
|
Cmd::new("notify")
|
||||||
|
.with("title", title)
|
||||||
|
.with("content", content)
|
||||||
|
.with("level", "warn")
|
||||||
|
.with("timeout", 5),
|
||||||
|
Layer::App
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
pub fn push(&mut self, msg: impl TryInto<Message>) {
|
pub fn push(&mut self, msg: impl TryInto<Message>) {
|
||||||
let Ok(mut msg) = msg.try_into() else {
|
let Ok(mut msg) = msg.try_into() else {
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use ratatui::layout::Rect;
|
||||||
use yazi_shared::{emit, event::Cmd, Layer};
|
use yazi_shared::{emit, event::Cmd, Layer};
|
||||||
|
|
||||||
use crate::notify::Notify;
|
use crate::notify::Notify;
|
||||||
|
|
@ -22,13 +23,13 @@ impl TryFrom<Cmd> for Opt {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Notify {
|
impl Notify {
|
||||||
pub fn tick(&mut self, opt: impl TryInto<Opt>) {
|
pub fn tick(&mut self, opt: impl TryInto<Opt>, area: Rect) {
|
||||||
self.tick_handle.take().map(|h| h.abort());
|
self.tick_handle.take().map(|h| h.abort());
|
||||||
let Ok(opt) = opt.try_into() else {
|
let Ok(opt) = opt.try_into() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let limit = self.limit();
|
let limit = self.limit(area);
|
||||||
if limit == 0 {
|
if limit == 0 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -44,7 +45,7 @@ impl Notify {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.messages.retain(|m| m.percent > 0 || !m.timeout.is_zero());
|
self.messages.retain(|m| m.percent > 0 || !m.timeout.is_zero());
|
||||||
let limit = self.limit();
|
let limit = self.limit(area);
|
||||||
let timeouts: Vec<_> = self.messages[..limit]
|
let timeouts: Vec<_> = self.messages[..limit]
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|&m| m.percent == 100 && !m.timeout.is_zero())
|
.filter(|&m| m.percent == 100 && !m.timeout.is_zero())
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
use unicode_width::UnicodeWidthStr;
|
||||||
use yazi_shared::event::Cmd;
|
use yazi_shared::event::Cmd;
|
||||||
|
|
||||||
use super::{Level, NOTIFY_BORDER};
|
use super::{Level, NOTIFY_BORDER};
|
||||||
|
|
@ -12,7 +13,6 @@ pub struct Message {
|
||||||
pub instant: Instant,
|
pub instant: Instant,
|
||||||
pub timeout: Duration,
|
pub timeout: Duration,
|
||||||
|
|
||||||
pub lines: usize,
|
|
||||||
pub percent: u8,
|
pub percent: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -26,7 +26,6 @@ impl TryFrom<Cmd> for Message {
|
||||||
}
|
}
|
||||||
|
|
||||||
let content = c.take_name("content").ok_or(())?;
|
let content = c.take_name("content").ok_or(())?;
|
||||||
let lines = content.lines().count();
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
title: c.take_name("title").ok_or(())?,
|
title: c.take_name("title").ok_or(())?,
|
||||||
content,
|
content,
|
||||||
|
|
@ -35,7 +34,6 @@ impl TryFrom<Cmd> for Message {
|
||||||
instant: Instant::now(),
|
instant: Instant::now(),
|
||||||
timeout: Duration::from_secs_f64(timeout),
|
timeout: Duration::from_secs_f64(timeout),
|
||||||
|
|
||||||
lines,
|
|
||||||
percent: 0,
|
percent: 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -43,5 +41,8 @@ impl TryFrom<Cmd> for Message {
|
||||||
|
|
||||||
impl Message {
|
impl Message {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn height(&self) -> usize { self.lines + NOTIFY_BORDER as usize }
|
pub fn height(&self, width: u16) -> usize {
|
||||||
|
let lines = (self.content.width() as f64 / width as f64).ceil();
|
||||||
|
lines as usize + NOTIFY_BORDER as usize
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use std::ops::ControlFlow;
|
use std::ops::ControlFlow;
|
||||||
|
|
||||||
|
use ratatui::layout::Rect;
|
||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
use yazi_shared::term::Term;
|
|
||||||
|
|
||||||
use super::{Message, NOTIFY_SPACING};
|
use super::{Message, NOTIFY_SPACING};
|
||||||
|
|
||||||
|
|
@ -12,14 +12,14 @@ pub struct Notify {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Notify {
|
impl Notify {
|
||||||
pub fn limit(&self) -> usize {
|
pub fn limit(&self, area: Rect) -> usize {
|
||||||
if self.messages.is_empty() {
|
if self.messages.is_empty() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut height = Term::size().height as usize;
|
let mut height = area.height as usize;
|
||||||
let flow = (0..self.messages.len().min(3)).try_fold(0, |acc, i| {
|
let flow = (0..self.messages.len().min(3)).try_fold(0, |acc, i| {
|
||||||
match height.checked_sub(self.messages[i].height() + NOTIFY_SPACING as usize) {
|
match height.checked_sub(self.messages[i].height(area.width) + NOTIFY_SPACING as usize) {
|
||||||
Some(h) => {
|
Some(h) => {
|
||||||
height = h;
|
height = h;
|
||||||
ControlFlow::Continue(acc + 1)
|
ControlFlow::Continue(acc + 1)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use yazi_shared::{event::Cmd, render, render_and};
|
use yazi_shared::{event::Cmd, render, render_and};
|
||||||
|
|
||||||
use crate::{manager::Manager, tab::{Mode, Tab}};
|
use crate::{manager::Manager, notify::Notify, tab::Tab};
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
pub struct Opt: u8 {
|
pub struct Opt: u8 {
|
||||||
|
|
@ -56,29 +56,17 @@ impl Tab {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn escape_find(&mut self) -> bool { render_and!(self.finder.take().is_some()) }
|
pub fn escape_find(&mut self) -> bool { render_and!(self.finder.take().is_some()) }
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn escape_visual(&mut self) -> bool {
|
pub fn escape_visual(&mut self) -> bool {
|
||||||
let Some((_, indices)) = self.mode.visual() else {
|
if !self.mode.is_visual() {
|
||||||
return false;
|
return false;
|
||||||
};
|
|
||||||
|
|
||||||
let state = self.mode.is_select();
|
|
||||||
for f in indices.iter().filter_map(|i| self.current.files.get(*i)) {
|
|
||||||
if state {
|
|
||||||
self.selected.add(&f.url);
|
|
||||||
} else {
|
|
||||||
self.selected.remove(&f.url);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.mode = Mode::Normal;
|
self.try_escape_visual();
|
||||||
render_and!(true)
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn escape_select(&mut self) -> bool {
|
pub fn escape_select(&mut self) -> bool {
|
||||||
if self.selected.is_empty() {
|
if self.selected.is_empty() {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -91,7 +79,6 @@ impl Tab {
|
||||||
render_and!(true)
|
render_and!(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn escape_filter(&mut self) -> bool {
|
pub fn escape_filter(&mut self) -> bool {
|
||||||
if self.current.files.filter().is_none() {
|
if self.current.files.filter().is_none() {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -101,7 +88,6 @@ impl Tab {
|
||||||
render_and!(true)
|
render_and!(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn escape_search(&mut self) -> bool {
|
pub fn escape_search(&mut self) -> bool {
|
||||||
if !self.current.cwd.is_search() {
|
if !self.current.cwd.is_search() {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -111,9 +97,34 @@ impl Tab {
|
||||||
render_and!(true)
|
render_and!(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn try_escape_visual(&mut self) -> bool {
|
pub fn try_escape_visual(&mut self) -> bool {
|
||||||
self.escape_visual();
|
let state = self.mode.is_select();
|
||||||
|
let Some((_, indices)) = self.mode.take_visual() else {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
let results: Vec<_> = indices
|
||||||
|
.iter()
|
||||||
|
.filter_map(|i| self.current.files.get(*i))
|
||||||
|
.map(|f| {
|
||||||
|
if state {
|
||||||
|
self.selected.add(&f.url)
|
||||||
|
} else {
|
||||||
|
self.selected.remove(&f.url);
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
render!(!results.is_empty());
|
||||||
|
if results.into_iter().all(|b| b) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Notify::_push_warn(
|
||||||
|
"Escape visual mode",
|
||||||
|
"Some files cannot be selected due to path nesting conflict.",
|
||||||
|
);
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use std::collections::BTreeSet;
|
use std::{collections::BTreeSet, mem};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||||
pub enum Mode {
|
pub enum Mode {
|
||||||
|
|
@ -9,8 +9,7 @@ pub enum Mode {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mode {
|
impl Mode {
|
||||||
#[inline]
|
pub fn visual_mut(&mut self) -> Option<(usize, &mut BTreeSet<usize>)> {
|
||||||
pub fn visual(&self) -> Option<(usize, &BTreeSet<usize>)> {
|
|
||||||
match self {
|
match self {
|
||||||
Mode::Normal => None,
|
Mode::Normal => None,
|
||||||
Mode::Select(start, indices) => Some((*start, indices)),
|
Mode::Select(start, indices) => Some((*start, indices)),
|
||||||
|
|
@ -18,12 +17,11 @@ impl Mode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
pub fn take_visual(&mut self) -> Option<(usize, BTreeSet<usize>)> {
|
||||||
pub fn visual_mut(&mut self) -> Option<(usize, &mut BTreeSet<usize>)> {
|
match mem::take(self) {
|
||||||
match self {
|
|
||||||
Mode::Normal => None,
|
Mode::Normal => None,
|
||||||
Mode::Select(start, indices) => Some((*start, indices)),
|
Mode::Select(start, indices) => Some((start, indices)),
|
||||||
Mode::Unset(start, indices) => Some((*start, indices)),
|
Mode::Unset(start, indices) => Some((start, indices)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use tokio::task::JoinHandle;
|
||||||
use yazi_shared::{fs::Url, render};
|
use yazi_shared::{fs::Url, render};
|
||||||
|
|
||||||
use super::{Backstack, Config, Finder, Mode, Preview};
|
use super::{Backstack, Config, Finder, Mode, Preview};
|
||||||
use crate::{folder::{Folder, FolderStage}, tab::selected::Selected};
|
use crate::{folder::{Folder, FolderStage}, tab::Selected};
|
||||||
|
|
||||||
pub struct Tab {
|
pub struct Tab {
|
||||||
pub mode: Mode,
|
pub mode: Mode,
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
use ratatui::{backend::{Backend, CrosstermBackend}, CompletedFrame};
|
use ratatui::{backend::{Backend, CrosstermBackend}, CompletedFrame};
|
||||||
|
|
||||||
use crate::{app::App, lives::Lives, notify::Notify, root::{Root, COLLISION}};
|
use crate::{app::App, lives::Lives, root::{Root, COLLISION}};
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
pub(crate) fn render(&mut self) {
|
pub(crate) fn render(&mut self) {
|
||||||
|
|
@ -45,7 +45,7 @@ impl App {
|
||||||
|
|
||||||
let frame = term
|
let frame = term
|
||||||
.draw_partial(|f| {
|
.draw_partial(|f| {
|
||||||
f.render_widget(Notify::new(&self.cx), f.size());
|
f.render_widget(crate::notify::Layout::new(&self.cx), f.size());
|
||||||
|
|
||||||
if let Some((x, y)) = self.cx.cursor() {
|
if let Some((x, y)) = self.cx.cursor() {
|
||||||
f.set_cursor(x, y);
|
f.set_cursor(x, y);
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,15 @@
|
||||||
use yazi_shared::event::Cmd;
|
use crossterm::terminal::WindowSize;
|
||||||
|
use ratatui::layout::Rect;
|
||||||
|
use yazi_shared::{event::Cmd, term::Term};
|
||||||
|
|
||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
pub(crate) fn update_notify(&mut self, cmd: Cmd) {
|
pub(crate) fn update_notify(&mut self, cmd: Cmd) {
|
||||||
self.cx.notify.tick(cmd);
|
let WindowSize { width, height, .. } = Term::size();
|
||||||
|
let area = crate::notify::Layout::available(Rect { x: 0, y: 0, width, height });
|
||||||
|
|
||||||
|
self.cx.notify.tick(cmd, area);
|
||||||
|
|
||||||
if self.cx.notify.messages.is_empty() {
|
if self.cx.notify.messages.is_empty() {
|
||||||
self.render();
|
self.render();
|
||||||
|
|
|
||||||
67
yazi-fm/src/notify/layout.rs
Normal file
67
yazi-fm/src/notify/layout.rs
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use ratatui::{buffer::Buffer, layout::{self, Constraint, Offset, Rect}, style::{Style, Stylize}, widgets::{Block, BorderType, Paragraph, Widget, Wrap}};
|
||||||
|
use yazi_core::notify::{Level, Message};
|
||||||
|
|
||||||
|
use crate::{widgets::Clear, Ctx};
|
||||||
|
|
||||||
|
pub(crate) struct Layout<'a> {
|
||||||
|
cx: &'a Ctx,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Layout<'a> {
|
||||||
|
pub(crate) fn new(cx: &'a Ctx) -> Self { Self { cx } }
|
||||||
|
|
||||||
|
pub(crate) fn available(area: Rect) -> Rect {
|
||||||
|
let chunks =
|
||||||
|
layout::Layout::horizontal([Constraint::Fill(1), Constraint::Length(40), Constraint::Max(1)])
|
||||||
|
.split(area);
|
||||||
|
|
||||||
|
let chunks =
|
||||||
|
layout::Layout::vertical([Constraint::Max(1), Constraint::Min(1)]).split(chunks[1]);
|
||||||
|
|
||||||
|
chunks[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tile(area: Rect, messages: &[Message]) -> Rc<[Rect]> {
|
||||||
|
layout::Layout::vertical(
|
||||||
|
messages.iter().map(|m| Constraint::Length(m.height(area.width) as u16)),
|
||||||
|
)
|
||||||
|
.spacing(1)
|
||||||
|
.split(area)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Widget for Layout<'a> {
|
||||||
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
|
let notify = &self.cx.notify;
|
||||||
|
|
||||||
|
let available = Self::available(area);
|
||||||
|
let limit = notify.limit(available);
|
||||||
|
let tile = Self::tile(available, ¬ify.messages[..limit]);
|
||||||
|
|
||||||
|
for (i, m) in notify.messages.iter().enumerate().take(limit) {
|
||||||
|
let (icon, style) = match m.level {
|
||||||
|
Level::Info => ("", Style::default().green()),
|
||||||
|
Level::Warn => ("", Style::default().yellow()),
|
||||||
|
Level::Error => ("", Style::default().red()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut rect =
|
||||||
|
tile[i].offset(Offset { x: (100 - m.percent) as i32 * tile[i].width as i32 / 100, y: 0 });
|
||||||
|
rect.width = area.width.saturating_sub(rect.x);
|
||||||
|
|
||||||
|
Clear.render(rect, buf);
|
||||||
|
Paragraph::new(m.content.as_str())
|
||||||
|
.wrap(Wrap { trim: false })
|
||||||
|
.block(
|
||||||
|
Block::bordered()
|
||||||
|
.border_type(BorderType::Rounded)
|
||||||
|
.title(format!("{} {}", icon, m.title))
|
||||||
|
.title_style(style)
|
||||||
|
.border_style(style),
|
||||||
|
)
|
||||||
|
.render(rect, buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
mod notify;
|
mod layout;
|
||||||
|
|
||||||
pub(super) use notify::*;
|
pub(super) use layout::*;
|
||||||
|
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use ratatui::{buffer::Buffer, layout::{Constraint, Layout, Offset, Rect}, style::{Style, Stylize}, widgets::{Block, BorderType, Paragraph, Widget}};
|
|
||||||
use yazi_core::notify::{Level, Message};
|
|
||||||
|
|
||||||
use crate::{widgets::Clear, Ctx};
|
|
||||||
|
|
||||||
pub(crate) struct Notify<'a> {
|
|
||||||
cx: &'a Ctx,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Notify<'a> {
|
|
||||||
pub(crate) fn new(cx: &'a Ctx) -> Self { Self { cx } }
|
|
||||||
|
|
||||||
fn chunks(area: Rect, messages: &[Message]) -> Rc<[Rect]> {
|
|
||||||
let chunks =
|
|
||||||
Layout::horizontal([Constraint::Fill(1), Constraint::Length(40), Constraint::Max(1)])
|
|
||||||
.split(area);
|
|
||||||
|
|
||||||
let chunks = Layout::vertical([Constraint::Max(1), Constraint::Min(1)]).split(chunks[1]);
|
|
||||||
|
|
||||||
Layout::vertical(messages.iter().map(|m| Constraint::Length(m.height() as u16)))
|
|
||||||
.spacing(1)
|
|
||||||
.split(chunks[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Widget for Notify<'a> {
|
|
||||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
|
||||||
let notify = &self.cx.notify;
|
|
||||||
|
|
||||||
let limit = notify.limit();
|
|
||||||
let chunks = Self::chunks(area, ¬ify.messages[..limit]);
|
|
||||||
|
|
||||||
for (i, m) in notify.messages.iter().enumerate().take(limit) {
|
|
||||||
let (icon, style) = match m.level {
|
|
||||||
Level::Info => ("", Style::default().green()),
|
|
||||||
Level::Warn => ("", Style::default().yellow()),
|
|
||||||
Level::Error => ("", Style::default().red()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut rect = chunks[i]
|
|
||||||
.offset(Offset { x: (100 - m.percent) as i32 * chunks[i].width as i32 / 100, y: 0 });
|
|
||||||
rect.width = area.width.saturating_sub(rect.x);
|
|
||||||
|
|
||||||
Clear.render(rect, buf);
|
|
||||||
Paragraph::new(m.content.as_str())
|
|
||||||
.block(
|
|
||||||
Block::bordered()
|
|
||||||
.border_type(BorderType::Rounded)
|
|
||||||
.title(format!("{} {}", icon, m.title))
|
|
||||||
.title_style(style)
|
|
||||||
.border_style(style),
|
|
||||||
)
|
|
||||||
.render(rect, buf);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Add table
Reference in a new issue