mirror of
https://github.com/sxyazi/yazi.git
synced 2024-12-19 23:01:36 +03:00
feat: custom manager layout (#76)
This commit is contained in:
parent
8c0ca4c649
commit
4f52d829bb
@ -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(),
|
||||
)
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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 = [ "<C-r>" ], exec = [ "redo" ] },
|
||||
{ on = [ "u" ], exec = "undo" },
|
||||
{ on = [ "<C-r>" ], exec = "redo" },
|
||||
]
|
||||
|
@ -1,4 +1,5 @@
|
||||
[manager]
|
||||
layout = [ 1, 4, 3 ]
|
||||
sort_by = "modified"
|
||||
sort_reverse = true
|
||||
sort_dir_first = true
|
||||
|
69
config/src/manager/layout.rs
Normal file
69
config/src/manager/layout.rs
Normal 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 }
|
||||
}
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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<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()
|
||||
}
|
||||
|
||||
@ -172,7 +167,7 @@ impl Folder {
|
||||
|
||||
pub fn paginate(&self) -> &Slice<PathBuf, File> {
|
||||
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<Rect> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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<PreviewData> {
|
||||
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<String> {
|
||||
Ok(
|
||||
external::jq(path, Self::rect().height as usize)
|
||||
.await?
|
||||
.lines()
|
||||
.take(Self::rect().height as usize)
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n"),
|
||||
)
|
||||
external::jq(path, MANAGER.layout.preview_height()).await
|
||||
}
|
||||
|
||||
pub async fn archive(path: &Path) -> Result<String> {
|
||||
@ -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::<Vec<_>>()
|
||||
.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");
|
||||
|
Loading…
Reference in New Issue
Block a user