feat: custom manager layout (#76)

This commit is contained in:
三咲雅 · Misaki Masa 2023-08-20 10:39:05 +08:00 committed by GitHub
parent 8c0ca4c649
commit 4f52d829bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 114 additions and 83 deletions

View File

@ -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 ratatui::{buffer::Buffer, layout::{self, Constraint, Direction, Rect}, widgets::{Block, Borders, Padding, Widget}};
use super::{Folder, Preview}; use super::{Folder, Preview};
@ -15,15 +14,16 @@ impl<'a> Layout<'a> {
impl<'a> Widget for Layout<'a> { impl<'a> Widget for Layout<'a> {
fn render(self, area: Rect, buf: &mut Buffer) { fn render(self, area: Rect, buf: &mut Buffer) {
let layout = &MANAGER.layout;
let manager = &self.cx.manager; let manager = &self.cx.manager;
let chunks = layout::Layout::new() let chunks = layout::Layout::new()
.direction(Direction::Horizontal) .direction(Direction::Horizontal)
.constraints( .constraints(
[ [
Constraint::Ratio(PARENT_RATIO, ALL_RATIO), Constraint::Ratio(layout.parent, layout.all),
Constraint::Ratio(CURRENT_RATIO, ALL_RATIO), Constraint::Ratio(layout.current, layout.all),
Constraint::Ratio(PREVIEW_RATIO, ALL_RATIO), Constraint::Ratio(layout.preview, layout.all),
] ]
.as_ref(), .as_ref(),
) )

View File

@ -1,7 +1,7 @@
use core::manager::{PreviewData, PREVIEW_BORDER}; use core::manager::PreviewData;
use ansi_to_tui::IntoText; 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 super::Folder;
use crate::Ctx; use crate::Ctx;
@ -16,16 +16,6 @@ impl<'a> Preview<'a> {
impl<'a> Widget for Preview<'a> { impl<'a> Widget for Preview<'a> {
fn render(self, area: Rect, buf: &mut Buffer) { 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 manager = &self.cx.manager;
let Some(hovered) = manager.hovered().map(|h| &h.path) else { let Some(hovered) = manager.hovered().map(|h| &h.path) else {
return; return;

View File

@ -2,6 +2,10 @@
## manager ## 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 - sort_by: File sorting method
- `"alphabetical"`: Sort alphabetically - `"alphabetical"`: Sort alphabetically

View File

@ -169,11 +169,11 @@ keymap = [
{ on = [ "x" ], exec = [ "delete --cut", "move 1 --in-operating" ] }, { on = [ "x" ], exec = [ "delete --cut", "move 1 --in-operating" ] },
# Yank/Paste # Yank/Paste
{ on = [ "y" ], exec = [ "yank" ] }, { on = [ "y" ], exec = "yank" },
{ on = [ "p" ], exec = [ "paste" ] }, { on = [ "p" ], exec = "paste" },
{ on = [ "P" ], exec = [ "paste --before" ] }, { on = [ "P" ], exec = "paste --before" },
# Undo/Redo # Undo/Redo
{ on = [ "u" ], exec = [ "undo" ] }, { on = [ "u" ], exec = "undo" },
{ on = [ "<C-r>" ], exec = [ "redo" ] }, { on = [ "<C-r>" ], exec = "redo" },
] ]

View File

@ -1,4 +1,5 @@
[manager] [manager]
layout = [ 1, 4, 3 ]
sort_by = "modified" sort_by = "modified"
sort_reverse = true sort_reverse = true
sort_dir_first = true sort_dir_first = true

View File

@ -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<u32>")]
pub struct ManagerLayout {
pub parent: u32,
pub current: u32,
pub preview: u32,
pub all: u32,
}
impl TryFrom<Vec<u32>> for ManagerLayout {
type Error = anyhow::Error;
fn try_from(ratio: Vec<u32>) -> Result<Self, Self::Error> {
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 }
}

View File

@ -1,10 +1,12 @@
use serde::Deserialize; use serde::Deserialize;
use super::SortBy; use super::{ManagerLayout, SortBy};
use crate::MERGED_YAZI; use crate::MERGED_YAZI;
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct Manager { pub struct Manager {
pub layout: ManagerLayout,
// Sorting // Sorting
pub sort_by: SortBy, pub sort_by: SortBy,
pub sort_reverse: bool, pub sort_reverse: bool,

View File

@ -1,5 +1,12 @@
mod layout;
mod manager; mod manager;
mod sorting; mod sorting;
pub use layout::*;
pub use manager::*; pub use manager::*;
pub use sorting::*; pub use sorting::*;
const FOLDER_MARGIN: u16 = 2;
const PREVIEW_BORDER: u16 = 2;
const PREVIEW_MARGIN: u16 = 2;

View File

@ -1,11 +1,9 @@
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use crossterm::terminal::WindowSize; use config::MANAGER;
use indexmap::map::Slice; use indexmap::map::Slice;
use ratatui::layout::Rect; use ratatui::layout::Rect;
use shared::Term;
use super::{ALL_RATIO, CURRENT_RATIO, DIR_PADDING, PARENT_RATIO};
use crate::{emit, files::{File, Files, FilesOp}}; use crate::{emit, files::{File, Files, FilesOp}};
#[derive(Default)] #[derive(Default)]
@ -27,9 +25,6 @@ impl Folder {
Self { cwd: cwd.to_path_buf(), in_search: true, ..Default::default() } 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 { pub fn update(&mut self, op: FilesOp) -> bool {
let b = match op { let b = match op {
FilesOp::Read(_, items) => self.files.update_read(items), FilesOp::Read(_, items) => self.files.update_read(items),
@ -55,7 +50,7 @@ impl Folder {
} }
pub fn set_page(&mut self, force: bool) -> bool { 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 }; let new = if limit == 0 { 0 } else { self.cursor / limit };
if !force && self.page == new { if !force && self.page == new {
return false; return false;
@ -77,7 +72,7 @@ impl Folder {
self.hovered = self.files.duplicate(self.cursor); self.hovered = self.files.duplicate(self.cursor);
self.set_page(false); 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) { if self.cursor >= (self.offset + limit).min(len).saturating_sub(5) {
self.offset = len.saturating_sub(limit).min(self.offset + self.cursor - old); self.offset = len.saturating_sub(limit).min(self.offset + self.cursor - old);
} }
@ -109,7 +104,7 @@ impl Folder {
#[inline] #[inline]
pub fn window(&self) -> &Slice<PathBuf, File> { pub fn window(&self) -> &Slice<PathBuf, File> {
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() self.files.get_range(self.offset..end).unwrap()
} }
@ -172,7 +167,7 @@ impl Folder {
pub fn paginate(&self) -> &Slice<PathBuf, File> { pub fn paginate(&self) -> &Slice<PathBuf, File> {
let len = self.files.len(); 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 start = (self.page * limit).min(len.saturating_sub(1));
let end = (start + limit).min(len); 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 has_selected(&self) -> bool { self.files.iter().any(|(_, f)| f.is_selected) }
pub fn rect_current(&self, path: &Path) -> Option<Rect> { pub fn rect_current(&self, path: &Path) -> Option<Rect> {
let pos = self.position(path)? - self.offset; let y = self.position(path)? - self.offset;
let WindowSize { columns, .. } = Term::size();
Some(Rect { let mut rect = MANAGER.layout.folder_rect();
x: (columns as u32 * PARENT_RATIO / ALL_RATIO) as u16, rect.y = rect.y.saturating_sub(1) + y as u16;
y: pos as u16, rect.height = 1;
width: (columns as u32 * CURRENT_RATIO / ALL_RATIO) as u16, Some(rect)
height: 1,
})
} }
} }

View File

@ -13,13 +13,3 @@ pub use preview::*;
pub use tab::*; pub use tab::*;
pub use tabs::*; pub use tabs::*;
pub use watcher::*; 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;

View File

@ -2,14 +2,11 @@ use std::{io::BufRead, mem, path::{Path, PathBuf}, sync::{atomic::{AtomicUsize,
use adaptor::Adaptor; use adaptor::Adaptor;
use anyhow::{anyhow, bail, Result}; use anyhow::{anyhow, bail, Result};
use config::{BOOT, PREVIEW}; use config::{BOOT, MANAGER, PREVIEW};
use crossterm::terminal::WindowSize; use shared::MimeKind;
use ratatui::prelude::Rect;
use shared::{MimeKind, Term};
use syntect::{easy::HighlightFile, util::as_24_bit_terminal_escaped}; use syntect::{easy::HighlightFile, util::as_24_bit_terminal_escaped};
use tokio::{fs, task::JoinHandle}; 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}; use crate::{emit, external, files::{Files, FilesOp}, highlighter};
#[derive(Default)] #[derive(Default)]
@ -31,20 +28,6 @@ pub enum PreviewData {
} }
impl Preview { 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) { pub fn go(&mut self, path: &Path, mime: &str, show_image: bool) {
let kind = MimeKind::new(mime); let kind = MimeKind::new(mime);
if !show_image && matches!(kind, MimeKind::Image | MimeKind::Video) { if !show_image && matches!(kind, MimeKind::Image | MimeKind::Video) {
@ -78,7 +61,7 @@ impl Preview {
pub fn reset(&mut self) -> bool { pub fn reset(&mut self) -> bool {
self.handle.take().map(|h| h.abort()); self.handle.take().map(|h| h.abort());
self.incr.fetch_add(1, Ordering::Relaxed); self.incr.fetch_add(1, Ordering::Relaxed);
Adaptor::image_hide(Self::rect()).ok(); Adaptor::image_hide(MANAGER.layout.preview_rect()).ok();
self.lock = None; self.lock = None;
!matches!( !matches!(
@ -90,7 +73,7 @@ impl Preview {
pub fn reset_image(&mut self) -> bool { pub fn reset_image(&mut self) -> bool {
self.handle.take().map(|h| h.abort()); self.handle.take().map(|h| h.abort());
self.incr.fetch_add(1, Ordering::Relaxed); 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) { if matches!(self.data, PreviewData::Image) {
self.lock = None; self.lock = None;
@ -109,7 +92,7 @@ impl Preview {
} }
pub async fn image(path: &Path) -> Result<PreviewData> { pub async fn image(path: &Path) -> Result<PreviewData> {
Adaptor::image_show(path, Self::rect()).await?; Adaptor::image_show(path, MANAGER.layout.preview_rect()).await?;
Ok(PreviewData::Image) Ok(PreviewData::Image)
} }
@ -132,14 +115,7 @@ impl Preview {
} }
pub async fn json(path: &Path) -> Result<String> { pub async fn json(path: &Path) -> Result<String> {
Ok( external::jq(path, MANAGER.layout.preview_height()).await
external::jq(path, Self::rect().height as usize)
.await?
.lines()
.take(Self::rect().height as usize)
.collect::<Vec<_>>()
.join("\n"),
)
} }
pub async fn archive(path: &Path) -> Result<String> { pub async fn archive(path: &Path) -> Result<String> {
@ -147,7 +123,7 @@ impl Preview {
external::lsar(path) external::lsar(path)
.await? .await?
.into_iter() .into_iter()
.take(Self::rect().height as usize) .take(MANAGER.layout.preview_height())
.map(|f| f.name) .map(|f| f.name)
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join("\n"), .join("\n"),
@ -165,7 +141,7 @@ impl Preview {
let mut line = String::new(); let mut line = String::new();
let mut buf = 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 { while i > 0 && h.reader.read_line(&mut line)? > 0 {
if tick != incr.load(Ordering::Relaxed) { if tick != incr.load(Ordering::Relaxed) {
bail!("Preview cancelled"); bail!("Preview cancelled");