mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
..
This commit is contained in:
parent
5d1591f591
commit
71c86a50c9
20 changed files with 336 additions and 205 deletions
|
|
@ -211,8 +211,8 @@ impl App {
|
|||
tasks.file_open(&targets);
|
||||
}
|
||||
}
|
||||
Event::Progress(percent, left) => {
|
||||
tasks.progress = (percent, left);
|
||||
Event::Progress(progress) => {
|
||||
tasks.progress = progress;
|
||||
emit!(Render);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
mod layout;
|
||||
mod progress;
|
||||
|
||||
pub(super) use layout::*;
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
use core::Ctx;
|
||||
|
||||
use config::THEME;
|
||||
use ratatui::{buffer::Buffer, layout::Rect, text::Span, widgets::{Gauge, Widget}};
|
||||
|
||||
pub(super) struct Progress<'a> {
|
||||
cx: &'a Ctx,
|
||||
}
|
||||
|
||||
impl<'a> Progress<'a> {
|
||||
pub(super) fn new(cx: &'a Ctx) -> Self { Self { cx } }
|
||||
}
|
||||
|
||||
impl<'a> Widget for Progress<'a> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let progress = &self.cx.tasks.progress;
|
||||
if progress.0 >= 100 {
|
||||
return;
|
||||
}
|
||||
|
||||
Gauge::default()
|
||||
.gauge_style(THEME.status.progress_gauge.into())
|
||||
.percent(progress.0 as u16)
|
||||
.label(Span::styled(
|
||||
format!("{:>3}%, {} left", progress.0, progress.1),
|
||||
THEME.status.progress_label.into(),
|
||||
))
|
||||
.render(area, buf);
|
||||
}
|
||||
}
|
||||
|
|
@ -14,8 +14,9 @@ mode_select = { fg = "#1E1E30", bg = "#D2A4FE", bold = true }
|
|||
mode_unset = { fg = "#1E1E30", bg = "#FFAF80", bold = true }
|
||||
|
||||
# Progress
|
||||
progress_label = { fg = "#FFFFFF", bold = true }
|
||||
progress_gauge = { fg = "#FFA577", bg = "#484D66" }
|
||||
progress_label = { fg = "#FFFFFF", bold = true }
|
||||
progress_normal = { fg = "#FFA577", bg = "#484D66" }
|
||||
progress_error = { fg = "#FF84A9", bg = "#484D66" }
|
||||
|
||||
# Permissions
|
||||
permissions_t = { fg = "#6D738F" }
|
||||
|
|
|
|||
|
|
@ -14,8 +14,9 @@ pub struct Status {
|
|||
pub mode_unset: Style,
|
||||
|
||||
// Progress
|
||||
pub progress_label: Style,
|
||||
pub progress_gauge: Style,
|
||||
pub progress_label: Style,
|
||||
pub progress_normal: Style,
|
||||
pub progress_error: Style,
|
||||
|
||||
// Permissions
|
||||
pub permissions_t: Style,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use shared::{InputError, RoCell, Url};
|
|||
use tokio::sync::{mpsc::{self, UnboundedSender}, oneshot};
|
||||
|
||||
use super::{files::{File, FilesOp}, input::InputOpt, select::SelectOpt};
|
||||
use crate::manager::PreviewLock;
|
||||
use crate::{manager::PreviewLock, tasks::TasksProgress};
|
||||
|
||||
static TX: RoCell<UnboundedSender<Event>> = RoCell::new();
|
||||
|
||||
|
|
@ -36,7 +36,7 @@ pub enum Event {
|
|||
|
||||
// Tasks
|
||||
Open(Vec<(OsString, String)>, Option<Opener>),
|
||||
Progress(u8, u32),
|
||||
Progress(TasksProgress),
|
||||
}
|
||||
|
||||
impl Event {
|
||||
|
|
@ -115,8 +115,8 @@ macro_rules! emit {
|
|||
(Open($targets:expr, $opener:expr)) => {
|
||||
$crate::Event::Open($targets, $opener).emit();
|
||||
};
|
||||
(Progress($percent:expr, $tasks:expr)) => {
|
||||
$crate::Event::Progress($percent, $tasks).emit();
|
||||
(Progress($progress:expr)) => {
|
||||
$crate::Event::Progress($progress).emit();
|
||||
};
|
||||
|
||||
($event:ident) => {
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ impl Running {
|
|||
match task.stage {
|
||||
TaskStage::Pending => return None,
|
||||
TaskStage::Dispatched => {
|
||||
if task.processed < task.found {
|
||||
if task.succ < task.total {
|
||||
return None;
|
||||
}
|
||||
if let Some(hook) = self.hooks.remove(&id) {
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
use std::{ffi::OsStr, sync::Arc, time::Duration};
|
||||
|
||||
use async_channel::{Receiver, Sender};
|
||||
use config::{open::Opener, TASKS};
|
||||
use futures::{future::BoxFuture, FutureExt};
|
||||
use parking_lot::RwLock;
|
||||
use shared::{unique_path, Throttle, Url};
|
||||
use tokio::{fs, select, sync::{mpsc::{self, UnboundedReceiver}, oneshot}, time::sleep};
|
||||
use tracing::{info, trace};
|
||||
|
||||
use super::{workers::{File, FileOpDelete, FileOpLink, FileOpPaste, FileOpTrash, Precache, PrecacheOpMime, PrecacheOpSize, Process, ProcessOpOpen}, Running, TaskOp, TaskStage};
|
||||
use super::{workers::{File, FileOpDelete, FileOpLink, FileOpPaste, FileOpTrash, Precache, PrecacheOpMime, PrecacheOpSize, Process, ProcessOpOpen}, Running, TaskOp, TaskStage, TasksProgress};
|
||||
use crate::emit;
|
||||
|
||||
pub struct Scheduler {
|
||||
|
|
@ -16,7 +14,8 @@ pub struct Scheduler {
|
|||
precache: Arc<Precache>,
|
||||
process: Arc<Process>,
|
||||
|
||||
todo: Sender<BoxFuture<'static, ()>>,
|
||||
todo: async_channel::Sender<BoxFuture<'static, ()>>,
|
||||
prog: mpsc::UnboundedSender<TaskOp>,
|
||||
pub(super) running: Arc<RwLock<Running>>,
|
||||
}
|
||||
|
||||
|
|
@ -28,9 +27,10 @@ impl Scheduler {
|
|||
let scheduler = Self {
|
||||
file: Arc::new(File::new(prog_tx.clone())),
|
||||
precache: Arc::new(Precache::new(prog_tx.clone())),
|
||||
process: Arc::new(Process::new(prog_tx)),
|
||||
process: Arc::new(Process::new(prog_tx.clone())),
|
||||
|
||||
todo: todo_tx,
|
||||
prog: prog_tx,
|
||||
running: Default::default(),
|
||||
};
|
||||
|
||||
|
|
@ -44,7 +44,7 @@ impl Scheduler {
|
|||
scheduler
|
||||
}
|
||||
|
||||
fn schedule_micro(&self, rx: Receiver<BoxFuture<'static, ()>>) {
|
||||
fn schedule_micro(&self, rx: async_channel::Receiver<BoxFuture<'static, ()>>) {
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
if let Ok(fut) = rx.recv().await {
|
||||
|
|
@ -54,9 +54,11 @@ impl Scheduler {
|
|||
});
|
||||
}
|
||||
|
||||
fn schedule_macro(&self, rx: Receiver<BoxFuture<'static, ()>>) {
|
||||
fn schedule_macro(&self, rx: async_channel::Receiver<BoxFuture<'static, ()>>) {
|
||||
let file = self.file.clone();
|
||||
let precache = self.precache.clone();
|
||||
|
||||
let prog = self.prog.clone();
|
||||
let running = self.running.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
|
|
@ -72,20 +74,18 @@ impl Scheduler {
|
|||
}
|
||||
Ok((id, mut op)) = file.recv() => {
|
||||
if !running.read().exists(id) {
|
||||
trace!("Skipping task {:?} as it was removed", op);
|
||||
continue;
|
||||
}
|
||||
if let Err(e) = file.work(&mut op).await {
|
||||
info!("Failed to work on task {:?}: {e}", op);
|
||||
prog.send(TaskOp::Fail(id, format!("Failed to work on this task: {:?}", e))).ok();
|
||||
}
|
||||
}
|
||||
Ok((id, mut op)) = precache.recv() => {
|
||||
if !running.read().exists(id) {
|
||||
trace!("Skipping task {:?} as it was removed", op);
|
||||
continue;
|
||||
}
|
||||
if let Err(e) = precache.work(&mut op).await {
|
||||
info!("Failed to work on task {:?}: {e}", op);
|
||||
prog.send(TaskOp::Fail(id, format!("Failed to work on this task: {:?}", e))).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -102,8 +102,36 @@ impl Scheduler {
|
|||
match op {
|
||||
TaskOp::New(id, size) => {
|
||||
if let Some(task) = running.write().get_mut(id) {
|
||||
task.found += 1;
|
||||
task.todo += size;
|
||||
task.total += 1;
|
||||
task.found += size;
|
||||
}
|
||||
}
|
||||
TaskOp::Adv(id, succ, processed) => {
|
||||
let mut running = running.write();
|
||||
if let Some(task) = running.get_mut(id) {
|
||||
task.succ += succ;
|
||||
task.processed += processed;
|
||||
}
|
||||
if succ > 0 {
|
||||
if let Some(fut) = running.try_remove(id, TaskStage::Pending) {
|
||||
todo.send_blocking(fut).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
TaskOp::Succ(id) => {
|
||||
if let Some(fut) = running.write().try_remove(id, TaskStage::Dispatched) {
|
||||
todo.send_blocking(fut).ok();
|
||||
}
|
||||
}
|
||||
TaskOp::Fail(id, reason) => {
|
||||
if let Some(task) = running.write().get_mut(id) {
|
||||
task.fail += 1;
|
||||
task.logs.push_str(&reason);
|
||||
task.logs.push('\n');
|
||||
|
||||
if let Some(logger) = &task.logger {
|
||||
logger.send(reason).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
TaskOp::Log(id, line) => {
|
||||
|
|
@ -116,62 +144,20 @@ impl Scheduler {
|
|||
}
|
||||
}
|
||||
}
|
||||
TaskOp::Adv(id, processed, size) => {
|
||||
let mut running = running.write();
|
||||
if let Some(task) = running.get_mut(id) {
|
||||
task.processed += processed;
|
||||
task.done += size;
|
||||
}
|
||||
if processed > 0 {
|
||||
if let Some(fut) = running.try_remove(id, TaskStage::Pending) {
|
||||
todo.send_blocking(fut).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
TaskOp::Done(id) => {
|
||||
if let Some(fut) = running.write().try_remove(id, TaskStage::Dispatched) {
|
||||
todo.send_blocking(fut).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let running = self.running.clone();
|
||||
let mut last = (100, 0);
|
||||
tokio::spawn(async move {
|
||||
let mut last = TasksProgress::default();
|
||||
loop {
|
||||
sleep(Duration::from_secs(1)).await;
|
||||
if running.read().is_empty() {
|
||||
if last != (100, 0) {
|
||||
last = (100, 0);
|
||||
emit!(Progress(last.0, last.1));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
sleep(Duration::from_millis(500)).await;
|
||||
|
||||
let mut tasks = 0u32;
|
||||
let mut left = 0;
|
||||
let mut progress = (0, 0);
|
||||
for task in running.read().values() {
|
||||
tasks += 1;
|
||||
left += task.found.saturating_sub(task.processed);
|
||||
progress = (progress.0 + task.done, progress.1 + task.todo);
|
||||
}
|
||||
|
||||
let mut percent = match progress.1 {
|
||||
0 => 100u8,
|
||||
_ => 100.min(progress.0 * 100 / progress.1) as u8,
|
||||
};
|
||||
|
||||
if tasks != 0 {
|
||||
percent = percent.min(99);
|
||||
left = left.max(1);
|
||||
}
|
||||
|
||||
if last != (percent, left) {
|
||||
last = (percent, left);
|
||||
emit!(Progress(last.0, last.1));
|
||||
let new = TasksProgress::from(&*running.read());
|
||||
if last != new {
|
||||
last = new;
|
||||
emit!(Progress(new));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,49 +1,36 @@
|
|||
use tokio::sync::mpsc;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Task {
|
||||
pub id: usize,
|
||||
pub name: String,
|
||||
pub stage: TaskStage,
|
||||
|
||||
pub found: u32,
|
||||
pub processed: u32,
|
||||
pub total: u32,
|
||||
pub succ: u32,
|
||||
pub fail: u32,
|
||||
|
||||
pub todo: u64,
|
||||
pub done: u64,
|
||||
pub found: u64,
|
||||
pub processed: u64,
|
||||
|
||||
pub logs: String,
|
||||
pub logger: Option<mpsc::UnboundedSender<String>>,
|
||||
}
|
||||
|
||||
impl Task {
|
||||
pub fn new(id: usize, name: String) -> Self { Self { id, name, ..Default::default() } }
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TaskSummary {
|
||||
pub name: String,
|
||||
|
||||
pub found: u32,
|
||||
pub processed: u32,
|
||||
pub total: u32,
|
||||
pub succ: u32,
|
||||
pub fail: u32,
|
||||
|
||||
pub todo: u64,
|
||||
pub done: u64,
|
||||
}
|
||||
|
||||
impl Task {
|
||||
pub fn new(id: usize, name: String) -> Self {
|
||||
Self {
|
||||
id,
|
||||
name,
|
||||
stage: Default::default(),
|
||||
|
||||
found: 0,
|
||||
processed: 0,
|
||||
|
||||
todo: 0,
|
||||
done: 0,
|
||||
|
||||
logs: Default::default(),
|
||||
logger: Default::default(),
|
||||
}
|
||||
}
|
||||
pub found: u64,
|
||||
pub processed: u64,
|
||||
}
|
||||
|
||||
impl From<&Task> for TaskSummary {
|
||||
|
|
@ -51,25 +38,28 @@ impl From<&Task> for TaskSummary {
|
|||
TaskSummary {
|
||||
name: task.name.clone(),
|
||||
|
||||
total: task.total,
|
||||
succ: task.succ,
|
||||
fail: task.fail,
|
||||
|
||||
found: task.found,
|
||||
processed: task.processed,
|
||||
|
||||
todo: task.todo,
|
||||
done: task.done,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TaskOp {
|
||||
// task_id, size
|
||||
// id, size
|
||||
New(usize, u64),
|
||||
// task_id, line
|
||||
Log(usize, String),
|
||||
// task_id, processed, size
|
||||
// id, processed, size
|
||||
Adv(usize, u32, u64),
|
||||
// task_id
|
||||
Done(usize),
|
||||
// id
|
||||
Succ(usize),
|
||||
// id
|
||||
Fail(usize, String),
|
||||
// id, line
|
||||
Log(usize, String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd)]
|
||||
|
|
|
|||
|
|
@ -2,11 +2,12 @@ use std::{collections::{BTreeMap, HashMap, HashSet}, ffi::OsStr, io::{stdout, Wr
|
|||
|
||||
use config::{manager::SortBy, open::Opener, OPEN};
|
||||
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
||||
use serde::Serialize;
|
||||
use shared::{Defer, MimeKind, Term, Url};
|
||||
use tokio::{io::{stdin, AsyncReadExt}, select, sync::mpsc, time};
|
||||
use tracing::trace;
|
||||
|
||||
use super::{task::TaskSummary, Scheduler, TASKS_PADDING, TASKS_PERCENT};
|
||||
use super::{running::Running, task::TaskSummary, Scheduler, TASKS_PADDING, TASKS_PERCENT};
|
||||
use crate::{emit, files::{File, Files}, input::InputOpt, Event, BLOCKER};
|
||||
|
||||
pub struct Tasks {
|
||||
|
|
@ -14,7 +15,7 @@ pub struct Tasks {
|
|||
|
||||
pub visible: bool,
|
||||
pub cursor: usize,
|
||||
pub progress: (u8, u32),
|
||||
pub progress: TasksProgress,
|
||||
}
|
||||
|
||||
impl Tasks {
|
||||
|
|
@ -23,7 +24,7 @@ impl Tasks {
|
|||
scheduler: Arc::new(Scheduler::start()),
|
||||
visible: false,
|
||||
cursor: 0,
|
||||
progress: (100, 0),
|
||||
progress: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -305,3 +306,32 @@ impl Tasks {
|
|||
#[inline]
|
||||
pub fn len(&self) -> usize { self.scheduler.running.read().len() }
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default, Eq, PartialEq, Serialize)]
|
||||
pub struct TasksProgress {
|
||||
pub total: u32,
|
||||
pub succ: u32,
|
||||
pub fail: u32,
|
||||
|
||||
pub found: u64,
|
||||
pub processed: u64,
|
||||
}
|
||||
|
||||
impl From<&Running> for TasksProgress {
|
||||
fn from(running: &Running) -> Self {
|
||||
let mut progress = Self::default();
|
||||
if running.is_empty() {
|
||||
return progress;
|
||||
}
|
||||
|
||||
for task in running.values() {
|
||||
progress.total += task.total;
|
||||
progress.succ += task.succ;
|
||||
progress.fail += task.fail;
|
||||
|
||||
progress.found += task.found;
|
||||
progress.processed += task.processed;
|
||||
}
|
||||
progress
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ use tracing::trace;
|
|||
use crate::tasks::TaskOp;
|
||||
|
||||
pub(crate) struct File {
|
||||
rx: async_channel::Receiver<FileOp>,
|
||||
tx: async_channel::Sender<FileOp>,
|
||||
rx: async_channel::Receiver<FileOp>,
|
||||
|
||||
sch: mpsc::UnboundedSender<TaskOp>,
|
||||
}
|
||||
|
|
@ -92,10 +92,7 @@ impl File {
|
|||
}
|
||||
break;
|
||||
}
|
||||
Ok(n) => {
|
||||
self.log(task.id, format!("Paste task advanced {n}: {:?}", task))?;
|
||||
self.sch.send(TaskOp::Adv(task.id, 0, n))?
|
||||
}
|
||||
Ok(n) => self.sch.send(TaskOp::Adv(task.id, 0, n))?,
|
||||
Err(e) if e.kind() == NotFound => {
|
||||
trace!("Paste task partially done: {:?}", task);
|
||||
break;
|
||||
|
|
@ -163,7 +160,7 @@ impl File {
|
|||
FileOp::Delete(task) => {
|
||||
if let Err(e) = fs::remove_file(&task.target).await {
|
||||
if e.kind() != NotFound && fs::symlink_metadata(&task.target).await.is_ok() {
|
||||
self.log(task.id, format!("Delete task failed: {:?}, {e}", task))?;
|
||||
self.fail(task.id, format!("Delete task failed: {:?}, {e}", task))?;
|
||||
Err(e)?
|
||||
}
|
||||
}
|
||||
|
|
@ -187,17 +184,11 @@ impl File {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn log(&self, id: usize, line: String) -> Result<()> { Ok(self.sch.send(TaskOp::Log(id, line))?) }
|
||||
|
||||
#[inline]
|
||||
fn done(&self, id: usize) -> Result<()> { Ok(self.sch.send(TaskOp::Done(id))?) }
|
||||
|
||||
pub(crate) async fn paste(&self, mut task: FileOpPaste) -> Result<()> {
|
||||
if task.cut {
|
||||
match fs::rename(&task.from, &task.to).await {
|
||||
Ok(_) => return self.done(task.id),
|
||||
Err(e) if e.kind() == NotFound => return self.done(task.id),
|
||||
Ok(_) => return self.succ(task.id),
|
||||
Err(e) if e.kind() == NotFound => return self.succ(task.id),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
@ -212,7 +203,20 @@ impl File {
|
|||
} else if meta.is_symlink() {
|
||||
self.tx.send(FileOp::Link(task.to_link(meta))).await?;
|
||||
}
|
||||
return self.done(id);
|
||||
return self.succ(id);
|
||||
}
|
||||
|
||||
macro_rules! continue_unless_ok {
|
||||
($result:expr) => {
|
||||
match $result {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
self.sch.send(TaskOp::New(task.id, 0))?;
|
||||
self.fail(task.id, format!("An error occurred while pasting: {e}"))?;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let root = task.to.clone();
|
||||
|
|
@ -221,27 +225,15 @@ impl File {
|
|||
|
||||
while let Some(src) = dirs.pop_front() {
|
||||
let dest = root.join(src.components().skip(skip).collect::<PathBuf>());
|
||||
match fs::create_dir(&dest).await {
|
||||
Err(e) if e.kind() != AlreadyExists => {
|
||||
self.log(task.id, format!("Create dir failed: {dest:?}, {e}"))?;
|
||||
continue;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let mut it = match fs::read_dir(&src).await {
|
||||
Ok(it) => it,
|
||||
Err(e) => {
|
||||
self.log(task.id, format!("Read dir failed: {src:?}, {e}"))?;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
continue_unless_ok!(match fs::create_dir(&dest).await {
|
||||
Err(e) if e.kind() != AlreadyExists => Err(e),
|
||||
_ => Ok(()),
|
||||
});
|
||||
|
||||
let mut it = continue_unless_ok!(fs::read_dir(&src).await);
|
||||
while let Ok(Some(entry)) = it.next_entry().await {
|
||||
let src = Url::from(entry.path());
|
||||
let Ok(meta) = Self::metadata(&src, task.follow).await else {
|
||||
continue;
|
||||
};
|
||||
let meta = continue_unless_ok!(Self::metadata(&src, task.follow).await);
|
||||
|
||||
if meta.is_dir() {
|
||||
dirs.push_back(src);
|
||||
|
|
@ -259,7 +251,7 @@ impl File {
|
|||
}
|
||||
}
|
||||
}
|
||||
self.done(task.id)
|
||||
self.succ(task.id)
|
||||
}
|
||||
|
||||
pub(crate) async fn link(&self, mut task: FileOpLink) -> Result<()> {
|
||||
|
|
@ -270,7 +262,7 @@ impl File {
|
|||
|
||||
self.sch.send(TaskOp::New(id, task.meta.as_ref().unwrap().len()))?;
|
||||
self.tx.send(FileOp::Link(task)).await?;
|
||||
self.done(id)
|
||||
self.succ(id)
|
||||
}
|
||||
|
||||
pub(crate) async fn delete(&self, mut task: FileOpDelete) -> Result<()> {
|
||||
|
|
@ -280,7 +272,7 @@ impl File {
|
|||
task.length = meta.len();
|
||||
self.sch.send(TaskOp::New(id, meta.len()))?;
|
||||
self.tx.send(FileOp::Delete(task)).await?;
|
||||
return self.done(id);
|
||||
return self.succ(id);
|
||||
}
|
||||
|
||||
let mut dirs = VecDeque::from([task.target]);
|
||||
|
|
@ -307,7 +299,7 @@ impl File {
|
|||
self.tx.send(FileOp::Delete(task.clone())).await?;
|
||||
}
|
||||
}
|
||||
self.done(task.id)
|
||||
self.succ(task.id)
|
||||
}
|
||||
|
||||
pub(crate) async fn trash(&self, mut task: FileOpTrash) -> Result<()> {
|
||||
|
|
@ -316,7 +308,7 @@ impl File {
|
|||
|
||||
self.sch.send(TaskOp::New(id, task.length))?;
|
||||
self.tx.send(FileOp::Trash(task)).await?;
|
||||
self.done(id)
|
||||
self.succ(id)
|
||||
}
|
||||
|
||||
async fn metadata(path: &Path, follow: bool) -> io::Result<Metadata> {
|
||||
|
|
@ -349,6 +341,19 @@ impl File {
|
|||
}
|
||||
}
|
||||
|
||||
impl File {
|
||||
#[inline]
|
||||
fn succ(&self, id: usize) -> Result<()> { Ok(self.sch.send(TaskOp::Succ(id))?) }
|
||||
|
||||
#[inline]
|
||||
fn fail(&self, id: usize, reason: String) -> Result<()> {
|
||||
Ok(self.sch.send(TaskOp::Fail(id, reason))?)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn log(&self, id: usize, line: String) -> Result<()> { Ok(self.sch.send(TaskOp::Log(id, line))?) }
|
||||
}
|
||||
|
||||
impl FileOpPaste {
|
||||
fn to_link(&self, meta: Metadata) -> FileOpLink {
|
||||
FileOpLink {
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ use tokio::{fs, sync::mpsc};
|
|||
use crate::{emit, external, files::FilesOp, tasks::TaskOp};
|
||||
|
||||
pub(crate) struct Precache {
|
||||
rx: async_channel::Receiver<PrecacheOp>,
|
||||
tx: async_channel::Sender<PrecacheOp>,
|
||||
rx: async_channel::Receiver<PrecacheOp>,
|
||||
|
||||
sch: mpsc::UnboundedSender<TaskOp>,
|
||||
|
||||
|
|
@ -105,9 +105,6 @@ impl Precache {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn done(&self, id: usize) -> Result<()> { Ok(self.sch.send(TaskOp::Done(id))?) }
|
||||
|
||||
pub(crate) async fn mime(&self, task: PrecacheOpMime) -> Result<()> {
|
||||
self.sch.send(TaskOp::New(task.id, 0))?;
|
||||
if let Ok(mimes) = external::file(&task.targets).await {
|
||||
|
|
@ -115,7 +112,7 @@ impl Precache {
|
|||
}
|
||||
|
||||
self.sch.send(TaskOp::Adv(task.id, 1, 0))?;
|
||||
self.done(task.id)
|
||||
self.succ(task.id)
|
||||
}
|
||||
|
||||
pub(crate) async fn size(&self, task: PrecacheOpSize) -> Result<()> {
|
||||
|
|
@ -133,7 +130,7 @@ impl Precache {
|
|||
});
|
||||
|
||||
self.sch.send(TaskOp::Adv(task.id, 1, 0))?;
|
||||
self.done(task.id)
|
||||
self.succ(task.id)
|
||||
}
|
||||
|
||||
pub(crate) fn image(&self, id: usize, targets: Vec<Url>) -> Result<()> {
|
||||
|
|
@ -141,7 +138,7 @@ impl Precache {
|
|||
self.sch.send(TaskOp::New(id, 0))?;
|
||||
self.tx.send_blocking(PrecacheOp::Image(PrecacheOpImage { id, target }))?;
|
||||
}
|
||||
self.done(id)
|
||||
self.succ(id)
|
||||
}
|
||||
|
||||
pub(crate) fn video(&self, id: usize, targets: Vec<Url>) -> Result<()> {
|
||||
|
|
@ -149,7 +146,7 @@ impl Precache {
|
|||
self.sch.send(TaskOp::New(id, 0))?;
|
||||
self.tx.send_blocking(PrecacheOp::Video(PrecacheOpVideo { id, target }))?;
|
||||
}
|
||||
self.done(id)
|
||||
self.succ(id)
|
||||
}
|
||||
|
||||
pub(crate) fn pdf(&self, id: usize, targets: Vec<Url>) -> Result<()> {
|
||||
|
|
@ -157,6 +154,11 @@ impl Precache {
|
|||
self.sch.send(TaskOp::New(id, 0))?;
|
||||
self.tx.send_blocking(PrecacheOp::Pdf(PrecacheOpPDF { id, target }))?;
|
||||
}
|
||||
self.done(id)
|
||||
self.succ(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl Precache {
|
||||
#[inline]
|
||||
fn succ(&self, id: usize) -> Result<()> { Ok(self.sch.send(TaskOp::Succ(id))?) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,12 +33,6 @@ impl From<&mut ProcessOpOpen> for ShellOpt {
|
|||
impl Process {
|
||||
pub(crate) fn new(sch: mpsc::UnboundedSender<TaskOp>) -> Self { Self { sch } }
|
||||
|
||||
#[inline]
|
||||
fn log(&self, id: usize, line: String) -> Result<()> { Ok(self.sch.send(TaskOp::Log(id, line))?) }
|
||||
|
||||
#[inline]
|
||||
fn done(&self, id: usize) -> Result<()> { Ok(self.sch.send(TaskOp::Done(id))?) }
|
||||
|
||||
pub(crate) async fn open(&self, mut task: ProcessOpOpen) -> Result<()> {
|
||||
let opt = ShellOpt::from(&mut task);
|
||||
if task.block {
|
||||
|
|
@ -48,11 +42,11 @@ impl Process {
|
|||
match external::shell(opt) {
|
||||
Ok(mut child) => {
|
||||
child.wait().await.ok();
|
||||
self.done(task.id)?;
|
||||
self.succ(task.id)?;
|
||||
}
|
||||
Err(e) => {
|
||||
self.sch.send(TaskOp::New(task.id, 0))?;
|
||||
self.log(task.id, format!("Failed to spawn process: {e}"))?;
|
||||
self.fail(task.id, format!("Failed to spawn process: {e}"))?;
|
||||
}
|
||||
}
|
||||
return Ok(emit!(Stop(false)).await);
|
||||
|
|
@ -60,10 +54,10 @@ impl Process {
|
|||
|
||||
if task.orphan {
|
||||
match external::shell(opt) {
|
||||
Ok(_) => self.done(task.id)?,
|
||||
Ok(_) => self.succ(task.id)?,
|
||||
Err(e) => {
|
||||
self.sch.send(TaskOp::New(task.id, 0))?;
|
||||
self.log(task.id, format!("Failed to spawn process: {e}"))?;
|
||||
self.fail(task.id, format!("Failed to spawn process: {e}"))?;
|
||||
}
|
||||
}
|
||||
return Ok(());
|
||||
|
|
@ -92,7 +86,7 @@ impl Process {
|
|||
None => "Process terminated by signal".to_string(),
|
||||
})?;
|
||||
if !status.success() {
|
||||
return Ok(());
|
||||
return self.fail(task.id, "Process failed".to_string());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -100,6 +94,19 @@ impl Process {
|
|||
}
|
||||
|
||||
self.sch.send(TaskOp::Adv(task.id, 1, 0))?;
|
||||
self.done(task.id)
|
||||
self.succ(task.id)
|
||||
}
|
||||
}
|
||||
|
||||
impl Process {
|
||||
#[inline]
|
||||
fn succ(&self, id: usize) -> Result<()> { Ok(self.sch.send(TaskOp::Succ(id))?) }
|
||||
|
||||
#[inline]
|
||||
fn fail(&self, id: usize, reason: String) -> Result<()> {
|
||||
Ok(self.sch.send(TaskOp::Fail(id, reason))?)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn log(&self, id: usize, line: String) -> Result<()> { Ok(self.sch.send(TaskOp::Log(id, line))?) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
{"flagWords":[],"words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","️ Überzug","️ Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit"],"language":"en","version":"0.2"}
|
||||
{"language":"en","flagWords":[],"words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","️ Überzug","️ Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ"],"version":"0.2"}
|
||||
|
|
|
|||
|
|
@ -99,6 +99,33 @@ function Status:position()
|
|||
}
|
||||
end
|
||||
|
||||
function Status:progress(area, offset)
|
||||
local progress = cx.tasks.progress
|
||||
local left = progress.total - progress.succ
|
||||
if left == 0 then
|
||||
return {}
|
||||
end
|
||||
|
||||
local gauge = ui.Gauge(ui.Rect {
|
||||
x = area.x + math.max(0, area.w - offset - 21),
|
||||
y = area.y,
|
||||
w = math.min(20, area.w),
|
||||
h = 1,
|
||||
})
|
||||
|
||||
local percent = 0
|
||||
if progress.processed ~= 0 then
|
||||
percent = math.floor(progress.processed * 100 / progress.found)
|
||||
end
|
||||
|
||||
return {
|
||||
gauge
|
||||
:gauge_style(THEME.status.progress_normal)
|
||||
:percent(percent)
|
||||
:label(ui.Span(string.format("%3d%%, %d left", percent, left)):style(THEME.status.progress_label)),
|
||||
}
|
||||
end
|
||||
|
||||
function Status:render(area)
|
||||
local chunks = ui.Layout()
|
||||
:direction(ui.Direction.HORIZONTAL)
|
||||
|
|
@ -107,5 +134,10 @@ function Status:render(area)
|
|||
|
||||
local left = ui.Line { self:mode(), self:size(), self:name() }
|
||||
local right = ui.Line { self:permissions(), self:percentage(), self:position() }
|
||||
return { ui.Paragraph(chunks[1], { left }), ui.Paragraph(chunks[2], { right }):align(ui.Alignment.RIGHT) }
|
||||
local progress = self:progress(chunks[2], right:width())
|
||||
return {
|
||||
ui.Paragraph(chunks[1], { left }),
|
||||
ui.Paragraph(chunks[2], { right }):align(ui.Alignment.RIGHT),
|
||||
table.unpack(progress),
|
||||
}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use mlua::{AnyUserData, Table, TableExt};
|
||||
|
||||
use crate::{layout::{List, Paragraph, Rect}, GLOBALS, LUA};
|
||||
use crate::{layout::{Gauge, List, Paragraph, Rect}, GLOBALS, LUA};
|
||||
|
||||
#[inline]
|
||||
fn layout(values: Vec<AnyUserData>, buf: &mut ratatui::prelude::Buffer) -> mlua::Result<()> {
|
||||
|
|
@ -9,6 +9,8 @@ fn layout(values: Vec<AnyUserData>, buf: &mut ratatui::prelude::Buffer) -> mlua:
|
|||
c.render(buf)
|
||||
} else if let Ok(c) = value.take::<List>() {
|
||||
c.render(buf)
|
||||
} else if let Ok(c) = value.take::<Gauge>() {
|
||||
c.render(buf)
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
|||
102
plugin/src/layout/gauge.rs
Normal file
102
plugin/src/layout/gauge.rs
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
use mlua::{AnyUserData, FromLua, Lua, Table, UserData, UserDataMethods, Value};
|
||||
use ratatui::widgets::Widget;
|
||||
|
||||
use super::{Rect, Span, Style};
|
||||
use crate::{GLOBALS, LUA};
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub(crate) struct Gauge {
|
||||
area: ratatui::layout::Rect,
|
||||
|
||||
ratio: f64,
|
||||
label: Option<ratatui::text::Span<'static>>,
|
||||
style: Option<ratatui::style::Style>,
|
||||
gauge_style: Option<ratatui::style::Style>,
|
||||
}
|
||||
|
||||
impl Gauge {
|
||||
pub(crate) fn install() -> mlua::Result<()> {
|
||||
let ui: Table = GLOBALS.get("ui")?;
|
||||
ui.set(
|
||||
"Gauge",
|
||||
LUA.create_function(|_, area: Rect| Ok(Gauge { area: area.0, ..Default::default() }))?,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn render(self, buf: &mut ratatui::buffer::Buffer) {
|
||||
let mut gauge = ratatui::widgets::Gauge::default();
|
||||
|
||||
gauge = gauge.ratio(self.ratio);
|
||||
if let Some(label) = self.label {
|
||||
gauge = gauge.label(label);
|
||||
}
|
||||
if let Some(style) = self.style {
|
||||
gauge = gauge.style(style);
|
||||
}
|
||||
if let Some(gauge_style) = self.gauge_style {
|
||||
gauge = gauge.gauge_style(gauge_style);
|
||||
}
|
||||
|
||||
gauge.render(self.area, buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'lua> FromLua<'lua> for Gauge {
|
||||
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> mlua::Result<Self> {
|
||||
match value {
|
||||
Value::UserData(ud) => Ok(ud.borrow::<Self>()?.clone()),
|
||||
_ => Err(mlua::Error::FromLuaConversionError {
|
||||
from: value.type_name(),
|
||||
to: "Gauge",
|
||||
message: Some("expected a Gauge".to_string()),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for Gauge {
|
||||
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_function("percent", |_, (ud, percent): (AnyUserData, u8)| {
|
||||
if percent > 100 {
|
||||
return Err(mlua::Error::RuntimeError("percent must be between 0 and 100".to_string()));
|
||||
}
|
||||
|
||||
ud.borrow_mut::<Self>()?.ratio = percent as f64 / 100.0;
|
||||
Ok(ud)
|
||||
});
|
||||
|
||||
methods.add_function("ratio", |_, (ud, ratio): (AnyUserData, f64)| {
|
||||
if !(0.0..1.0).contains(&ratio) {
|
||||
return Err(mlua::Error::RuntimeError("ratio must be between 0 and 1".to_string()));
|
||||
}
|
||||
|
||||
ud.borrow_mut::<Self>()?.ratio = ratio;
|
||||
Ok(ud)
|
||||
});
|
||||
|
||||
methods.add_function("label", |_, (ud, label): (AnyUserData, Span)| {
|
||||
ud.borrow_mut::<Self>()?.label = Some(label.0);
|
||||
Ok(ud)
|
||||
});
|
||||
|
||||
methods.add_function("style", |_, (ud, value): (AnyUserData, Value)| {
|
||||
ud.borrow_mut::<Self>()?.style = match value {
|
||||
Value::Nil => None,
|
||||
Value::Table(tbl) => Some(Style::from(tbl).0),
|
||||
Value::UserData(ud) => Some(ud.borrow::<Style>()?.0),
|
||||
_ => return Err(mlua::Error::external("expected a Style or Table or nil")),
|
||||
};
|
||||
Ok(ud)
|
||||
});
|
||||
|
||||
methods.add_function("gauge_style", |_, (ud, value): (AnyUserData, Value)| {
|
||||
ud.borrow_mut::<Self>()?.gauge_style = match value {
|
||||
Value::Nil => None,
|
||||
Value::Table(tbl) => Some(Style::from(tbl).0),
|
||||
Value::UserData(ud) => Some(ud.borrow::<Style>()?.0),
|
||||
_ => return Err(mlua::Error::external("expected a Style or Table or nil")),
|
||||
};
|
||||
Ok(ud)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -50,6 +50,7 @@ impl<'lua> FromLua<'lua> for Line {
|
|||
|
||||
impl UserData for Line {
|
||||
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_function("width", |_, ud: AnyUserData| Ok(ud.borrow_mut::<Self>()?.0.width()));
|
||||
methods.add_function("style", |_, (ud, value): (AnyUserData, Value)| {
|
||||
{
|
||||
let mut me = ud.borrow_mut::<Self>()?;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#![allow(clippy::module_inception)]
|
||||
|
||||
mod constraint;
|
||||
mod gauge;
|
||||
mod layout;
|
||||
mod line;
|
||||
mod list;
|
||||
|
|
@ -10,6 +11,7 @@ mod span;
|
|||
mod style;
|
||||
|
||||
pub(super) use constraint::*;
|
||||
pub(super) use gauge::*;
|
||||
pub(super) use layout::*;
|
||||
pub(super) use line::*;
|
||||
pub(super) use list::*;
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ pub fn init() {
|
|||
crate::Config.install()?;
|
||||
|
||||
layout::Constraint::install()?;
|
||||
layout::Gauge::install()?;
|
||||
layout::Layout::install()?;
|
||||
layout::Line::install()?;
|
||||
layout::List::install()?;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue