mirror of
https://github.com/sxyazi/yazi.git
synced 2024-12-20 07:11:33 +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 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(),
|
||||||
)
|
)
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
|
@ -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" },
|
||||||
]
|
]
|
||||||
|
@ -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
|
||||||
|
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 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,
|
||||||
|
@ -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;
|
||||||
|
@ -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,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
|
||||||
|
@ -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");
|
||||||
|
Loading…
Reference in New Issue
Block a user