From 4f52d829bb52b672ea033b2fab819ed0585c4406 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E9=9B=85=20=C2=B7=20Misaki=20Masa?= Date: Sun, 20 Aug 2023 10:39:05 +0800 Subject: [PATCH] feat: custom manager layout (#76) --- app/src/manager/layout.rs | 10 ++--- app/src/manager/preview.rs | 14 +------ config/docs/yazi.md | 4 ++ config/preset/keymap.toml | 10 ++--- config/preset/yazi.toml | 1 + config/src/manager/layout.rs | 69 +++++++++++++++++++++++++++++++++++ config/src/manager/manager.rs | 4 +- config/src/manager/mod.rs | 7 ++++ core/src/manager/folder.rs | 28 +++++--------- core/src/manager/mod.rs | 10 ----- core/src/manager/preview.rs | 40 ++++---------------- 11 files changed, 114 insertions(+), 83 deletions(-) create mode 100644 config/src/manager/layout.rs diff --git a/app/src/manager/layout.rs b/app/src/manager/layout.rs index 5d401da6..2bb88817 100644 --- a/app/src/manager/layout.rs +++ b/app/src/manager/layout.rs @@ -1,5 +1,4 @@ -use core::manager::{ALL_RATIO, CURRENT_RATIO, PARENT_RATIO, PREVIEW_RATIO}; - +use config::MANAGER; use ratatui::{buffer::Buffer, layout::{self, Constraint, Direction, Rect}, widgets::{Block, Borders, Padding, Widget}}; use super::{Folder, Preview}; @@ -15,15 +14,16 @@ impl<'a> Layout<'a> { impl<'a> Widget for Layout<'a> { fn render(self, area: Rect, buf: &mut Buffer) { + let layout = &MANAGER.layout; let manager = &self.cx.manager; let chunks = layout::Layout::new() .direction(Direction::Horizontal) .constraints( [ - Constraint::Ratio(PARENT_RATIO, ALL_RATIO), - Constraint::Ratio(CURRENT_RATIO, ALL_RATIO), - Constraint::Ratio(PREVIEW_RATIO, ALL_RATIO), + Constraint::Ratio(layout.parent, layout.all), + Constraint::Ratio(layout.current, layout.all), + Constraint::Ratio(layout.preview, layout.all), ] .as_ref(), ) diff --git a/app/src/manager/preview.rs b/app/src/manager/preview.rs index 475223a4..e7dbbb73 100644 --- a/app/src/manager/preview.rs +++ b/app/src/manager/preview.rs @@ -1,7 +1,7 @@ -use core::manager::{PreviewData, PREVIEW_BORDER}; +use core::manager::PreviewData; use ansi_to_tui::IntoText; -use ratatui::{buffer::Buffer, layout::Rect, widgets::{Clear, Paragraph, Widget}}; +use ratatui::{buffer::Buffer, layout::Rect, widgets::{Paragraph, Widget}}; use super::Folder; use crate::Ctx; @@ -16,16 +16,6 @@ impl<'a> Preview<'a> { impl<'a> Widget for Preview<'a> { fn render(self, area: Rect, buf: &mut Buffer) { - Clear.render( - Rect { - x: area.x, - y: area.y, - width: area.width + PREVIEW_BORDER / 2, - height: area.height, - }, - buf, - ); - let manager = &self.cx.manager; let Some(hovered) = manager.hovered().map(|h| &h.path) else { return; diff --git a/config/docs/yazi.md b/config/docs/yazi.md index c39a8135..b375b8b3 100644 --- a/config/docs/yazi.md +++ b/config/docs/yazi.md @@ -2,6 +2,10 @@ ## manager +- layout: Manager layout by ratio, 3-element array + + - `[1, 4, 3]`: 1/8 width for parent, 4/8 width for current, 3/8 width for preview + - sort_by: File sorting method - `"alphabetical"`: Sort alphabetically diff --git a/config/preset/keymap.toml b/config/preset/keymap.toml index 661665ea..25757fa3 100644 --- a/config/preset/keymap.toml +++ b/config/preset/keymap.toml @@ -169,11 +169,11 @@ keymap = [ { on = [ "x" ], exec = [ "delete --cut", "move 1 --in-operating" ] }, # Yank/Paste - { on = [ "y" ], exec = [ "yank" ] }, - { on = [ "p" ], exec = [ "paste" ] }, - { on = [ "P" ], exec = [ "paste --before" ] }, + { on = [ "y" ], exec = "yank" }, + { on = [ "p" ], exec = "paste" }, + { on = [ "P" ], exec = "paste --before" }, # Undo/Redo - { on = [ "u" ], exec = [ "undo" ] }, - { on = [ "" ], exec = [ "redo" ] }, + { on = [ "u" ], exec = "undo" }, + { on = [ "" ], exec = "redo" }, ] diff --git a/config/preset/yazi.toml b/config/preset/yazi.toml index 2238353a..1b5edd40 100644 --- a/config/preset/yazi.toml +++ b/config/preset/yazi.toml @@ -1,4 +1,5 @@ [manager] +layout = [ 1, 4, 3 ] sort_by = "modified" sort_reverse = true sort_dir_first = true diff --git a/config/src/manager/layout.rs b/config/src/manager/layout.rs new file mode 100644 index 00000000..8ea2fdba --- /dev/null +++ b/config/src/manager/layout.rs @@ -0,0 +1,69 @@ +use anyhow::bail; +use crossterm::terminal::WindowSize; +use ratatui::prelude::Rect; +use serde::Deserialize; +use shared::Term; + +use super::{FOLDER_MARGIN, PREVIEW_BORDER, PREVIEW_MARGIN}; + +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq)] +#[serde(try_from = "Vec")] +pub struct ManagerLayout { + pub parent: u32, + pub current: u32, + pub preview: u32, + pub all: u32, +} + +impl TryFrom> for ManagerLayout { + type Error = anyhow::Error; + + fn try_from(ratio: Vec) -> Result { + if ratio.len() != 3 { + bail!("invalid layout ratio: {:?}", ratio); + } + if ratio.iter().all(|&r| r == 0) { + bail!("at least one layout ratio must be non-zero: {:?}", ratio); + } + + Ok(Self { + parent: ratio[0], + current: ratio[1], + preview: ratio[2], + all: ratio[0] + ratio[1] + ratio[2], + }) + } +} + +impl ManagerLayout { + pub fn preview_rect(&self) -> Rect { + let WindowSize { columns, rows, .. } = Term::size(); + + let x = (columns as u32 * (self.parent + self.current) / self.all) as u16; + let width = (columns as u32 * self.preview / self.all) as u16; + + Rect { + x: x.saturating_add(PREVIEW_BORDER / 2), + y: PREVIEW_MARGIN / 2, + width: width.saturating_sub(PREVIEW_BORDER), + height: rows.saturating_sub(PREVIEW_MARGIN), + } + } + + #[inline] + pub fn preview_height(&self) -> usize { self.preview_rect().height as usize } + + pub fn folder_rect(&self) -> Rect { + let WindowSize { columns, rows, .. } = Term::size(); + + Rect { + x: (columns as u32 * self.parent / self.all) as u16, + y: FOLDER_MARGIN / 2, + width: (columns as u32 * self.current / self.all) as u16, + height: rows.saturating_sub(FOLDER_MARGIN), + } + } + + #[inline] + pub fn folder_height(&self) -> usize { self.folder_rect().height as usize } +} diff --git a/config/src/manager/manager.rs b/config/src/manager/manager.rs index 90c013e3..ddc1627a 100644 --- a/config/src/manager/manager.rs +++ b/config/src/manager/manager.rs @@ -1,10 +1,12 @@ use serde::Deserialize; -use super::SortBy; +use super::{ManagerLayout, SortBy}; use crate::MERGED_YAZI; #[derive(Debug, Deserialize)] pub struct Manager { + pub layout: ManagerLayout, + // Sorting pub sort_by: SortBy, pub sort_reverse: bool, diff --git a/config/src/manager/mod.rs b/config/src/manager/mod.rs index 6bf4de2e..4e72e379 100644 --- a/config/src/manager/mod.rs +++ b/config/src/manager/mod.rs @@ -1,5 +1,12 @@ +mod layout; mod manager; mod sorting; +pub use layout::*; pub use manager::*; pub use sorting::*; + +const FOLDER_MARGIN: u16 = 2; + +const PREVIEW_BORDER: u16 = 2; +const PREVIEW_MARGIN: u16 = 2; diff --git a/core/src/manager/folder.rs b/core/src/manager/folder.rs index 377bd800..9d5bb333 100644 --- a/core/src/manager/folder.rs +++ b/core/src/manager/folder.rs @@ -1,11 +1,9 @@ use std::path::{Path, PathBuf}; -use crossterm::terminal::WindowSize; +use config::MANAGER; use indexmap::map::Slice; use ratatui::layout::Rect; -use shared::Term; -use super::{ALL_RATIO, CURRENT_RATIO, DIR_PADDING, PARENT_RATIO}; use crate::{emit, files::{File, Files, FilesOp}}; #[derive(Default)] @@ -27,9 +25,6 @@ impl Folder { Self { cwd: cwd.to_path_buf(), in_search: true, ..Default::default() } } - #[inline] - pub fn limit() -> usize { Term::size().rows.saturating_sub(DIR_PADDING) as usize } - pub fn update(&mut self, op: FilesOp) -> bool { let b = match op { FilesOp::Read(_, items) => self.files.update_read(items), @@ -55,7 +50,7 @@ impl Folder { } pub fn set_page(&mut self, force: bool) -> bool { - let limit = Self::limit(); + let limit = MANAGER.layout.folder_height(); let new = if limit == 0 { 0 } else { self.cursor / limit }; if !force && self.page == new { return false; @@ -77,7 +72,7 @@ impl Folder { self.hovered = self.files.duplicate(self.cursor); self.set_page(false); - let limit = Self::limit(); + let limit = MANAGER.layout.folder_height(); if self.cursor >= (self.offset + limit).min(len).saturating_sub(5) { self.offset = len.saturating_sub(limit).min(self.offset + self.cursor - old); } @@ -109,7 +104,7 @@ impl Folder { #[inline] pub fn window(&self) -> &Slice { - let end = (self.offset + Self::limit()).min(self.files.len()); + let end = (self.offset + MANAGER.layout.folder_height()).min(self.files.len()); self.files.get_range(self.offset..end).unwrap() } @@ -172,7 +167,7 @@ impl Folder { pub fn paginate(&self) -> &Slice { let len = self.files.len(); - let limit = Self::limit(); + let limit = MANAGER.layout.folder_height(); let start = (self.page * limit).min(len.saturating_sub(1)); let end = (start + limit).min(len); @@ -183,14 +178,11 @@ impl Folder { pub fn has_selected(&self) -> bool { self.files.iter().any(|(_, f)| f.is_selected) } pub fn rect_current(&self, path: &Path) -> Option { - let pos = self.position(path)? - self.offset; - let WindowSize { columns, .. } = Term::size(); + let y = self.position(path)? - self.offset; - Some(Rect { - x: (columns as u32 * PARENT_RATIO / ALL_RATIO) as u16, - y: pos as u16, - width: (columns as u32 * CURRENT_RATIO / ALL_RATIO) as u16, - height: 1, - }) + let mut rect = MANAGER.layout.folder_rect(); + rect.y = rect.y.saturating_sub(1) + y as u16; + rect.height = 1; + Some(rect) } } diff --git a/core/src/manager/mod.rs b/core/src/manager/mod.rs index da18f7bc..751ed877 100644 --- a/core/src/manager/mod.rs +++ b/core/src/manager/mod.rs @@ -13,13 +13,3 @@ pub use preview::*; pub use tab::*; pub use tabs::*; pub use watcher::*; - -pub const PARENT_RATIO: u32 = 1; -pub const CURRENT_RATIO: u32 = 4; -pub const PREVIEW_RATIO: u32 = 3; -pub const ALL_RATIO: u32 = PARENT_RATIO + CURRENT_RATIO + PREVIEW_RATIO; - -pub const DIR_PADDING: u16 = 2; - -pub const PREVIEW_BORDER: u16 = 2; -pub const PREVIEW_MARGIN: u16 = 2; diff --git a/core/src/manager/preview.rs b/core/src/manager/preview.rs index e55aa843..98456dc1 100644 --- a/core/src/manager/preview.rs +++ b/core/src/manager/preview.rs @@ -2,14 +2,11 @@ use std::{io::BufRead, mem, path::{Path, PathBuf}, sync::{atomic::{AtomicUsize, use adaptor::Adaptor; use anyhow::{anyhow, bail, Result}; -use config::{BOOT, PREVIEW}; -use crossterm::terminal::WindowSize; -use ratatui::prelude::Rect; -use shared::{MimeKind, Term}; +use config::{BOOT, MANAGER, PREVIEW}; +use shared::MimeKind; use syntect::{easy::HighlightFile, util::as_24_bit_terminal_escaped}; use tokio::{fs, task::JoinHandle}; -use super::{ALL_RATIO, CURRENT_RATIO, PARENT_RATIO, PREVIEW_BORDER, PREVIEW_MARGIN, PREVIEW_RATIO}; use crate::{emit, external, files::{Files, FilesOp}, highlighter}; #[derive(Default)] @@ -31,20 +28,6 @@ pub enum PreviewData { } impl Preview { - fn rect() -> Rect { - let WindowSize { columns, rows, .. } = Term::size(); - - let x = (columns as u32 * (PARENT_RATIO + CURRENT_RATIO) / ALL_RATIO) as u16; - let width = (columns as u32 * PREVIEW_RATIO / ALL_RATIO) as u16; - - Rect { - x: x.saturating_add(PREVIEW_BORDER / 2), - y: PREVIEW_MARGIN / 2, - width: width.saturating_sub(PREVIEW_BORDER), - height: rows.saturating_sub(PREVIEW_MARGIN), - } - } - pub fn go(&mut self, path: &Path, mime: &str, show_image: bool) { let kind = MimeKind::new(mime); if !show_image && matches!(kind, MimeKind::Image | MimeKind::Video) { @@ -78,7 +61,7 @@ impl Preview { pub fn reset(&mut self) -> bool { self.handle.take().map(|h| h.abort()); self.incr.fetch_add(1, Ordering::Relaxed); - Adaptor::image_hide(Self::rect()).ok(); + Adaptor::image_hide(MANAGER.layout.preview_rect()).ok(); self.lock = None; !matches!( @@ -90,7 +73,7 @@ impl Preview { pub fn reset_image(&mut self) -> bool { self.handle.take().map(|h| h.abort()); self.incr.fetch_add(1, Ordering::Relaxed); - Adaptor::image_hide(Self::rect()).ok(); + Adaptor::image_hide(MANAGER.layout.preview_rect()).ok(); if matches!(self.data, PreviewData::Image) { self.lock = None; @@ -109,7 +92,7 @@ impl Preview { } pub async fn image(path: &Path) -> Result { - Adaptor::image_show(path, Self::rect()).await?; + Adaptor::image_show(path, MANAGER.layout.preview_rect()).await?; Ok(PreviewData::Image) } @@ -132,14 +115,7 @@ impl Preview { } pub async fn json(path: &Path) -> Result { - Ok( - external::jq(path, Self::rect().height as usize) - .await? - .lines() - .take(Self::rect().height as usize) - .collect::>() - .join("\n"), - ) + external::jq(path, MANAGER.layout.preview_height()).await } pub async fn archive(path: &Path) -> Result { @@ -147,7 +123,7 @@ impl Preview { external::lsar(path) .await? .into_iter() - .take(Self::rect().height as usize) + .take(MANAGER.layout.preview_height()) .map(|f| f.name) .collect::>() .join("\n"), @@ -165,7 +141,7 @@ impl Preview { let mut line = String::new(); let mut buf = String::new(); - let mut i = Self::rect().height as usize; + let mut i = MANAGER.layout.preview_height(); while i > 0 && h.reader.read_line(&mut line)? > 0 { if tick != incr.load(Ordering::Relaxed) { bail!("Preview cancelled");