refactor: separate file logic from the folder module

This commit is contained in:
sxyazi 2023-07-10 16:01:48 +08:00
parent 66ac5f9d9c
commit ac6b077bd2
No known key found for this signature in database
23 changed files with 303 additions and 190 deletions

View file

@ -7,7 +7,7 @@ mod pattern;
pub mod preview;
pub mod theme;
pub use pattern::*;
pub(crate) use pattern::*;
pub static KEYMAP: Lazy<keymap::Keymap> = Lazy::new(|| keymap::Keymap::new());
pub static MANAGER: Lazy<manager::Manager> = Lazy::new(|| manager::Manager::new());

View file

@ -1 +1,3 @@
pub mod kitty;
mod kitty;
pub use kitty::*;

View file

@ -2,10 +2,9 @@ use std::path::PathBuf;
use anyhow::Result;
use crossterm::event::KeyEvent;
use indexmap::IndexMap;
use tokio::sync::{mpsc::Sender, oneshot};
use super::{FolderItem, InputOpt, PreviewData};
use super::{files::FilesOp, input::InputOpt, manager::PreviewData};
static mut TX: Option<Sender<Event>> = None;
@ -17,7 +16,7 @@ pub enum Event {
Resize(u16, u16),
Refresh,
Files(PathBuf, IndexMap<PathBuf, FolderItem>),
Files(FilesOp),
Hover,
Mimetype(PathBuf, String),
Preview(PathBuf, PreviewData),
@ -67,8 +66,8 @@ macro_rules! emit {
$crate::core::Event::Resize($cols, $rows).emit();
};
(Files($path:expr, $items:expr)) => {
$crate::core::Event::Files($path, $items).emit();
(Files($op:expr)) => {
$crate::core::Event::Files($op).emit();
};
(Mimetype($path:expr, $mime:expr)) => {
$crate::core::Event::Mimetype($path, $mime).emit();

View file

@ -12,7 +12,7 @@ pub struct FdOpt {
pub subject: String,
}
pub fn fd(opt: FdOpt) -> Result<(JoinHandle<()>, UnboundedReceiver<Vec<String>>)> {
pub fn fd(opt: FdOpt) -> Result<(JoinHandle<()>, UnboundedReceiver<Vec<PathBuf>>)> {
let mut child = Command::new("fd")
.arg("--base-directory")
.arg(&opt.cwd)
@ -30,7 +30,10 @@ pub fn fd(opt: FdOpt) -> Result<(JoinHandle<()>, UnboundedReceiver<Vec<String>>)
let handle = tokio::spawn(async move {
while let Ok(Some(line)) = it.next_line().await {
buf.push(line);
let path = PathBuf::from(line);
if path.components().count() > 1 {
buf.push(path);
}
}
child.wait().await.ok();
});

View file

@ -11,7 +11,7 @@ pub struct RgOpt {
pub subject: String,
}
pub async fn rg(opt: RgOpt) -> Result<(JoinHandle<()>, UnboundedReceiver<Vec<String>>)> {
pub fn rg(opt: RgOpt) -> Result<(JoinHandle<()>, UnboundedReceiver<Vec<PathBuf>>)> {
let mut child = Command::new("rg")
.current_dir(&opt.cwd)
.args(&["--color=never", "--files-with-matches", "--smart-case"])
@ -28,7 +28,10 @@ pub async fn rg(opt: RgOpt) -> Result<(JoinHandle<()>, UnboundedReceiver<Vec<Str
let handle = tokio::spawn(async move {
while let Ok(Some(line)) = it.next_line().await {
buf.push(line);
let path = PathBuf::from(line);
if path.components().count() > 1 {
buf.push(path);
}
}
child.wait().await.ok();
});

35
src/core/files/file.rs Normal file
View file

@ -0,0 +1,35 @@
use std::{fs::Metadata, path::{Path, PathBuf}};
use anyhow::Result;
use tokio::fs;
#[derive(Clone)]
pub struct File {
pub name: String,
pub path: PathBuf,
pub meta: Metadata,
pub length: Option<u64>,
pub is_link: bool,
pub is_hidden: bool,
pub is_selected: bool,
}
impl File {
#[inline]
pub async fn from(path: &Path) -> Result<File> {
let meta = fs::metadata(path).await?;
Ok(Self::from_meta(path, meta).await)
}
pub async fn from_meta(path: &Path, mut meta: Metadata) -> File {
let is_link = meta.is_symlink();
if is_link {
meta = fs::metadata(&path).await.unwrap_or(meta);
}
let name = path.file_name().map(|n| n.to_string_lossy().to_string()).unwrap_or_default();
let length = if meta.is_dir() { None } else { Some(meta.len()) };
let is_hidden = name.starts_with('.');
File { name, path: path.to_path_buf(), meta, length, is_link, is_hidden, is_selected: false }
}
}

134
src/core/files/files.rs Normal file
View file

@ -0,0 +1,134 @@
use std::{ops::{Deref, DerefMut}, path::{Path, PathBuf}};
use indexmap::IndexMap;
use tokio::fs;
use super::File;
use crate::{config::{manager::SortBy, MANAGER}, emit};
#[derive(Default)]
pub struct Files {
items: IndexMap<PathBuf, File>,
sort: FilesSort,
pub show_hidden: bool,
}
impl Files {
pub async fn from(paths: Vec<PathBuf>) -> IndexMap<PathBuf, File> {
let mut items = IndexMap::new();
for path in paths {
if let Ok(file) = File::from(&path).await {
items.insert(path, file);
}
}
items
}
pub async fn read(path: &Path) {
let mut iter = match fs::read_dir(path).await {
Ok(it) => it,
Err(_) => return,
};
let mut items = IndexMap::new();
while let Ok(Some(item)) = iter.next_entry().await {
if let Ok(meta) = item.metadata().await {
let path = item.path();
let file = File::from_meta(&path, meta).await;
items.insert(path, file);
}
}
emit!(Files(FilesOp::Update(path.to_path_buf(), items)));
}
pub fn sort(&mut self) {
fn cmp<T: Ord>(a: T, b: T, reverse: bool) -> std::cmp::Ordering {
if reverse { b.cmp(&a) } else { a.cmp(&b) }
}
let reverse = self.sort.reverse;
match self.sort.by {
SortBy::Alphabetical => self.items.sort_by(|_, a, _, b| cmp(&a.name, &b.name, reverse)),
SortBy::Created => self.items.sort_by(|_, a, _, b| {
if let (Ok(a), Ok(b)) = (a.meta.created(), b.meta.created()) {
return cmp(a, b, reverse);
}
std::cmp::Ordering::Equal
}),
SortBy::Modified => self.items.sort_by(|_, a, _, b| {
if let (Ok(a), Ok(b)) = (a.meta.modified(), b.meta.modified()) {
return cmp(a, b, reverse);
}
std::cmp::Ordering::Equal
}),
SortBy::Size => {
self.items.sort_by(|_, a, _, b| cmp(a.length.unwrap_or(0), b.length.unwrap_or(0), reverse))
}
}
}
pub fn update(&mut self, mut items: IndexMap<PathBuf, File>) -> bool {
if !self.show_hidden {
items.retain(|_, item| !item.is_hidden);
}
for (path, item) in &mut items {
if let Some(old) = self.items.get(path) {
item.length = old.length;
item.is_selected = old.is_selected;
}
}
self.items = items;
self.sort();
true
}
pub fn append(&mut self, items: IndexMap<PathBuf, File>) -> bool {
for (path, mut item) in items.into_iter() {
if let Some(old) = self.items.get(&path) {
item.length = old.length;
item.is_selected = old.is_selected;
}
self.items.insert(path, item);
}
self.sort();
true
}
}
impl Deref for Files {
type Target = IndexMap<PathBuf, File>;
fn deref(&self) -> &Self::Target { &self.items }
}
impl DerefMut for Files {
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.items }
}
struct FilesSort {
pub by: SortBy,
pub reverse: bool,
}
impl Default for FilesSort {
fn default() -> Self { Self { by: MANAGER.sort_by, reverse: MANAGER.sort_reverse } }
}
pub enum FilesOp {
Update(PathBuf, IndexMap<PathBuf, File>),
Append(PathBuf, IndexMap<PathBuf, File>),
}
impl FilesOp {
#[inline]
pub fn path(&self) -> PathBuf {
match self {
Self::Update(path, _) => path,
Self::Append(path, _) => path,
}
.clone()
}
}

5
src/core/files/mod.rs Normal file
View file

@ -0,0 +1,5 @@
mod file;
mod files;
pub use file::*;
pub use files::*;

View file

@ -1,142 +1,48 @@
use std::{fs::Metadata, path::{Path, PathBuf}, usize};
use std::{path::{Path, PathBuf}, usize};
use indexmap::{map::Slice, IndexMap};
use indexmap::map::Slice;
use ratatui::layout::Rect;
use tokio::fs;
use super::{ALL_RATIO, CURRENT_RATIO, DIR_PADDING, PARENT_RATIO};
use crate::{config::{manager::SortBy, MANAGER}, emit, misc::tty_size};
use crate::{core::files::{File, Files, FilesOp}, emit, misc::tty_size};
#[derive(Default)]
pub struct Folder {
pub cwd: PathBuf,
items: IndexMap<PathBuf, FolderItem>,
offset: usize,
cursor: usize,
pub cwd: PathBuf,
pub files: Files,
offset: usize,
cursor: usize,
sort: FolderSort,
show_hidden: bool,
pub in_search: bool,
}
#[derive(Clone)]
pub struct FolderItem {
pub name: String,
pub path: PathBuf,
pub meta: Metadata,
pub length: Option<u64>,
pub is_link: bool,
pub is_hidden: bool,
pub is_selected: bool,
}
struct FolderSort {
pub by: SortBy,
pub reverse: bool,
}
impl Default for FolderSort {
fn default() -> Self { Self { by: MANAGER.sort_by, reverse: MANAGER.sort_reverse } }
}
impl Folder {
pub fn new(cwd: &Path) -> Self {
Self { cwd: cwd.to_path_buf(), show_hidden: MANAGER.show_hidden, ..Default::default() }
}
pub fn new(cwd: &Path) -> Self { Self { cwd: cwd.to_path_buf(), ..Default::default() } }
pub fn new_search(cwd: &Path) -> Self {
Self {
cwd: cwd.to_path_buf(),
show_hidden: MANAGER.show_hidden,
in_search: true,
..Default::default()
Self { cwd: cwd.to_path_buf(), in_search: true, ..Default::default() }
}
pub fn update(&mut self, op: FilesOp) -> bool {
let b = match op {
FilesOp::Update(_, items) => self.files.update(items),
FilesOp::Append(_, items) => self.files.append(items),
};
if !b {
return false;
}
let len = self.files.len();
self.cursor = self.cursor.min(len.saturating_sub(1));
self.offset = self.offset.min(len);
true
}
#[inline]
pub fn limit() -> usize { tty_size().ws_row.saturating_sub(DIR_PADDING) as usize }
pub async fn read(path: &Path) {
let mut iter = match fs::read_dir(path).await {
Ok(it) => it,
Err(_) => return,
};
let mut items = IndexMap::new();
while let Ok(Some(item)) = iter.next_entry().await {
let mut meta = if let Ok(meta) = item.metadata().await { meta } else { continue };
let is_link = meta.is_symlink();
if is_link {
meta = fs::metadata(&path).await.unwrap_or(meta);
}
let path = item.path();
let name = item.file_name().to_string_lossy().to_string();
let length = if meta.is_dir() { None } else { Some(meta.len()) };
let is_hidden = name.starts_with('.');
items.insert(path.clone(), FolderItem {
name,
path,
meta,
length,
is_link,
is_hidden,
is_selected: false,
});
}
emit!(Files(path.to_path_buf(), items));
}
pub fn sort(&mut self) {
fn cmp<T: Ord>(a: T, b: T, reverse: bool) -> std::cmp::Ordering {
if reverse { b.cmp(&a) } else { a.cmp(&b) }
}
let reverse = self.sort.reverse;
match self.sort.by {
SortBy::Alphabetical => self.items.sort_by(|_, a, _, b| cmp(&a.name, &b.name, reverse)),
SortBy::Created => self.items.sort_by(|_, a, _, b| {
if let (Ok(a), Ok(b)) = (a.meta.created(), b.meta.created()) {
return cmp(a, b, reverse);
}
std::cmp::Ordering::Equal
}),
SortBy::Modified => self.items.sort_by(|_, a, _, b| {
if let (Ok(a), Ok(b)) = (a.meta.modified(), b.meta.modified()) {
return cmp(a, b, reverse);
}
std::cmp::Ordering::Equal
}),
SortBy::Size => {
self.items.sort_by(|_, a, _, b| cmp(a.length.unwrap_or(0), b.length.unwrap_or(0), reverse))
}
}
}
pub fn update(&mut self, mut items: IndexMap<PathBuf, FolderItem>) -> bool {
if !self.show_hidden {
items.retain(|_, item| !item.is_hidden);
}
for (path, item) in &mut items {
if let Some(old) = self.items.get(path) {
item.length = old.length;
item.is_selected = old.is_selected;
}
}
let len = items.len();
self.items = items;
self.cursor = self.cursor.min(len.saturating_sub(1));
self.offset = self.offset.min(len);
self.sort();
true
}
pub fn next(&mut self, step: usize) -> bool {
let len = self.items.len();
let len = self.files.len();
if len == 0 {
return false;
}
@ -164,30 +70,30 @@ impl Folder {
}
pub fn hidden(&mut self, show: Option<bool>) -> bool {
if show.is_none() || self.show_hidden != show.unwrap() {
self.show_hidden = !self.show_hidden;
if show.is_none() || self.files.show_hidden != show.unwrap() {
self.files.show_hidden = !self.files.show_hidden;
emit!(Refresh);
}
false
}
pub fn paginate(&self) -> &Slice<PathBuf, FolderItem> {
let end = (self.offset + Self::limit()).min(self.items.len());
self.items.get_range(self.offset..end).unwrap()
pub fn paginate(&self) -> &Slice<PathBuf, File> {
let end = (self.offset + Self::limit()).min(self.files.len());
self.files.get_range(self.offset..end).unwrap()
}
pub fn select(&mut self, idx: Option<usize>, state: Option<bool>) -> bool {
let len = self.items.len();
let len = self.files.len();
let mut apply = |idx: usize, state: Option<bool>| -> bool {
if state.is_none() {
self.items[idx].is_selected = !self.items[idx].is_selected;
self.files[idx].is_selected = !self.files[idx].is_selected;
return true;
}
let state = state.unwrap();
if state != self.items[idx].is_selected {
self.items[idx].is_selected = state;
if state != self.files[idx].is_selected {
self.files[idx].is_selected = state;
return true;
}
@ -213,7 +119,7 @@ impl Folder {
pub fn selected(&self) -> Option<Vec<PathBuf>> {
let v = self
.items
.files
.iter()
.filter(|(_, item)| item.is_selected)
.map(|(path, _)| path.clone())
@ -234,9 +140,7 @@ impl Folder {
impl Folder {
#[inline]
pub fn hovered(&self) -> Option<&FolderItem> {
self.items.get_index(self.cursor).map(|(_, item)| item)
}
pub fn hovered(&self) -> Option<&File> { self.files.get_index(self.cursor).map(|(_, item)| item) }
#[inline]
pub fn cursor(&self) -> usize { self.cursor }
@ -246,7 +150,7 @@ impl Folder {
#[inline]
pub fn position(&self, path: &Path) -> Option<usize> {
self.items.iter().position(|(p, _)| p == path)
self.files.iter().position(|(p, _)| p == path)
}
#[inline]

View file

@ -1,12 +1,9 @@
use std::{collections::{BTreeSet, HashMap, HashSet}, path::PathBuf};
use indexmap::IndexMap;
use ratatui::layout::Rect;
use tokio::fs;
use tracing::trace;
use super::{FolderItem, PreviewData, Tab, Tabs, Watcher};
use crate::{core::{Folder, Input, InputOpt, Precache}, emit};
use super::{PreviewData, Tab, Tabs, Watcher};
use crate::{core::{files::{File, FilesOp}, input::{Input, InputOpt}, manager::Folder, tasks::Precache}, emit};
pub struct Manager {
tabs: Tabs,
@ -169,25 +166,20 @@ impl Manager {
files.into_iter().map(|p| self.mimetype.get(p).cloned()).collect()
}
pub fn update_files(&mut self, path: PathBuf, items: IndexMap<PathBuf, FolderItem>) -> bool {
pub fn update_files(&mut self, op: FilesOp) -> bool {
let path = op.path();
let cwd = self.current().cwd.clone();
let hovered = self.hovered().map(|h| h.path.clone());
let mut b = if cwd == path && !self.current().in_search {
self.current_mut().update(items)
let folder = if cwd == path && !self.current().in_search {
self.current_mut()
} else if matches!(self.parent(), Some(p) if p.cwd == path) {
self.active_mut().parent.as_mut().unwrap().update(items)
self.active_mut().parent.as_mut().unwrap()
} else {
self
.active_mut()
.history
.entry(path.clone())
.or_insert_with(|| Folder::new(&path))
.update(items);
matches!(self.hovered(), Some(h) if h.path == path)
self.active_mut().history.entry(path.to_path_buf()).or_insert_with(|| Folder::new(&path))
};
let mut b = folder.update(op) || matches!(self.hovered(), Some(h) if h.path == path);
b |= self.active_mut().parent.as_mut().map_or(false, |p| p.hover(&cwd));
b |= hovered.as_ref().map_or(false, |h| self.current_mut().hover(h));
@ -248,5 +240,5 @@ impl Manager {
pub fn parent(&self) -> &Option<Folder> { &self.tabs.active().parent }
#[inline]
pub fn hovered(&self) -> Option<&FolderItem> { self.tabs.active().current.hovered() }
pub fn hovered(&self) -> Option<&File> { self.tabs.active().current.hovered() }
}

View file

@ -1,14 +1,12 @@
use std::{fs::File, io::BufReader, path::{Path, PathBuf}, sync::OnceLock};
use adapter::kitty::Kitty;
use anyhow::{anyhow, Context, Result};
use image::imageops::FilterType;
use ratatui::layout::Rect;
use syntect::{easy::HighlightLines, highlighting::{Theme, ThemeSet}, parsing::SyntaxSet, util::as_24_bit_terminal_escaped};
use tokio::{fs, task::JoinHandle};
use super::{Folder, ALL_RATIO, PREVIEW_BORDER, PREVIEW_PADDING, PREVIEW_RATIO};
use crate::{config::{PREVIEW, THEME}, core::{adapter, Precache}, emit, misc::{first_n_lines, tty_ratio, tty_size}};
use super::{ALL_RATIO, PREVIEW_BORDER, PREVIEW_PADDING, PREVIEW_RATIO};
use crate::{config::{PREVIEW, THEME}, core::{adapter::Kitty, files::Files, tasks::Precache}, emit, misc::{first_n_lines, tty_ratio, tty_size}};
static SYNTECT_SYNTAX: OnceLock<SyntaxSet> = OnceLock::new();
static SYNTECT_THEME: OnceLock<Theme> = OnceLock::new();
@ -76,7 +74,7 @@ impl Preview {
}
pub async fn folder(path: &Path) -> Result<PreviewData> {
Folder::read(&path).await;
Files::read(&path).await;
Ok(PreviewData::Folder)
}

View file

@ -1,11 +1,15 @@
use std::{collections::BTreeMap, mem, path::{Path, PathBuf}};
use anyhow::Result;
use tokio::task::JoinHandle;
use super::{Folder, Mode, Preview};
use crate::emit;
use crate::{core::{external, files::{Files, FilesOp}, input::{Input, InputOpt}}, emit};
pub struct Tab {
pub(super) current: Folder,
pub(super) parent: Option<Folder>,
search: Option<JoinHandle<Result<()>>>,
pub(super) mode: Mode,
@ -18,6 +22,7 @@ impl Tab {
Self {
current: Folder::new(path),
parent: path.parent().map(|p| Folder::new(p)),
search: None,
mode: Default::default(),
@ -124,6 +129,40 @@ impl Tab {
pub fn forward(&mut self) -> bool { todo!() }
pub fn search(&mut self, grep: bool) -> bool {
if let Some(handle) = self.search.take() {
handle.abort();
}
let cwd = self.current.cwd.clone();
let hidden = self.current.files.show_hidden;
let pos = Input::top_position();
self.search = Some(tokio::spawn(async move {
let subject = emit!(Input(InputOpt {
title: "Find:".to_string(),
value: "".to_string(),
position: pos,
}))
.await?;
let (handle, mut buf) = if grep {
external::rg(external::RgOpt { cwd: cwd.clone(), hidden, subject })?
} else {
external::fd(external::FdOpt { cwd: cwd.clone(), hidden, regex: false, subject })?
};
while let Some(chunk) = buf.recv().await {
if chunk.is_empty() {
break;
}
emit!(Files(FilesOp::Append(cwd.clone(), Files::from(chunk).await)));
}
Ok(handle.await?)
}));
false
}
pub fn select(&mut self, state: Option<bool>) -> bool {
let idx = Some(self.current.cursor());
self.current.select(idx, state)

View file

@ -3,8 +3,7 @@ use std::{collections::BTreeSet, path::{Path, PathBuf}};
use notify::{RecommendedWatcher, Watcher as _Watcher};
use tokio::sync::mpsc::{self, Sender};
use super::Folder;
use crate::emit;
use crate::core::files::Files;
pub struct Watcher {
tx: Sender<PathBuf>,
@ -54,7 +53,7 @@ impl Watcher {
tokio::spawn(async move {
while let Some(path) = rx.recv().await {
Folder::read(&path).await;
Files::read(&path).await;
}
});

View file

@ -1,12 +1,9 @@
mod adapter;
pub mod adapter;
mod event;
pub mod external;
mod input;
mod manager;
mod tasks;
pub mod files;
pub mod input;
pub mod manager;
pub mod tasks;
pub use adapter::*;
pub use event::*;
pub use input::*;
pub use manager::*;
pub use tasks::*;

View file

@ -31,5 +31,8 @@ impl<T> DelayedBuffer<T> {
}
impl<T> Drop for DelayedBuffer<T> {
fn drop(&mut self) { self.flush(); }
fn drop(&mut self) {
self.flush();
self.tx.send(vec![]).ok();
}
}

View file

@ -74,8 +74,8 @@ impl App {
Event::Refresh => {
manager.refresh();
}
Event::Files(path, files) => {
if manager.update_files(path, files) {
Event::Files(op) => {
if manager.update_files(op) {
emit!(Render);
}
}

View file

@ -1,4 +1,4 @@
use crate::core::{Input, Manager, Tasks};
use crate::core::{input::Input, manager::Manager, tasks::Tasks};
pub struct Ctx {
pub cursor: Option<(u16, u16)>,

View file

@ -1,7 +1,7 @@
use crossterm::event::KeyCode;
use super::Ctx;
use crate::{config::{keymap::{Exec, Key, Single}, KEYMAP}, core::InputMode, emit, misc::optinal_bool};
use crate::{config::{keymap::{Exec, Key, Single}, KEYMAP}, core::input::InputMode, emit, misc::optinal_bool};
pub struct Executor;

View file

@ -1,7 +1,7 @@
use ratatui::{buffer::Buffer, layout::Rect, style::{Color, Style}, text::Line, widgets::{Block, BorderType, Borders, Clear, Paragraph, Widget}};
use super::{Ctx, Term};
use crate::core::InputMode;
use crate::core::input::InputMode;
pub struct Input<'a> {
cx: &'a Ctx,

View file

@ -3,13 +3,13 @@ use ratatui::{buffer::Buffer, layout::Rect, style::{Color, Modifier, Style}, wid
use crate::{config::THEME, core};
pub struct Folder<'a> {
folder: &'a core::Folder,
folder: &'a core::manager::Folder,
is_preview: bool,
is_selection: bool,
}
impl<'a> Folder<'a> {
pub fn new(folder: &'a core::Folder) -> Self {
pub fn new(folder: &'a core::manager::Folder) -> Self {
Self { folder, is_preview: false, is_selection: false }
}

View file

@ -1,7 +1,7 @@
use ratatui::{buffer::Buffer, layout::{self, Constraint, Direction, Rect}, widgets::{Block, Borders, Widget}};
use super::{Folder, Preview};
use crate::{core::{Mode, ALL_RATIO, CURRENT_RATIO, PARENT_RATIO, PREVIEW_RATIO}, ui::Ctx};
use crate::{core::manager::{Mode, ALL_RATIO, CURRENT_RATIO, PARENT_RATIO, PREVIEW_RATIO}, ui::Ctx};
pub struct Layout<'a> {
cx: &'a Ctx,

View file

@ -4,7 +4,7 @@ use ansi_to_tui::IntoText;
use ratatui::{buffer::Buffer, layout::Rect, widgets::{Paragraph, Widget}};
use super::Folder;
use crate::{core::{kitty::Kitty, PreviewData}, ui::{Ctx, Term}};
use crate::{core::{adapter::Kitty, manager::PreviewData}, ui::{Ctx, Term}};
pub struct Preview<'a> {
cx: &'a Ctx,

View file

@ -1,7 +1,7 @@
use ratatui::{buffer::Buffer, layout::{self, Alignment, Constraint, Direction, Rect}, style::{Color, Modifier, Style}, widgets::{Block, BorderType, Borders, List, ListItem, Padding, Widget}};
use super::Clear;
use crate::{core::TASKS_PERCENT, ui::Ctx};
use crate::{core::tasks::TASKS_PERCENT, ui::Ctx};
pub struct Layout<'a> {
cx: &'a Ctx,