mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
Merge 62584cfddc into 782f88b965
This commit is contained in:
commit
250192f239
7 changed files with 213 additions and 47 deletions
|
|
@ -9,19 +9,22 @@ use tokio::fs;
|
||||||
pub struct Image;
|
pub struct Image;
|
||||||
|
|
||||||
impl Image {
|
impl Image {
|
||||||
pub(super) async fn crop(path: &Path, size: (u16, u16)) -> Result<DynamicImage> {
|
pub(super) async fn crop(
|
||||||
let (w, h) = Term::ratio()
|
path: &Path,
|
||||||
.map(|(w, h)| {
|
(target_width, target_height): (u16, u16),
|
||||||
let (w, h) = ((size.0 as f64 * w) as u32, (size.1 as f64 * h) as u32);
|
) -> Result<DynamicImage> {
|
||||||
(w.min(PREVIEW.max_width), h.min(PREVIEW.max_height))
|
let (max_width, max_height) = Term::ratio()
|
||||||
|
.map(|(a, b)| {
|
||||||
|
let (width, height) = ((target_width as f64 * a) as u32, (target_height as f64 * b) as u32);
|
||||||
|
(width.min(PREVIEW.max_width), height.min(PREVIEW.max_height))
|
||||||
})
|
})
|
||||||
.unwrap_or((PREVIEW.max_width, PREVIEW.max_height));
|
.unwrap_or((PREVIEW.max_width, PREVIEW.max_height));
|
||||||
|
|
||||||
let img = fs::read(path).await?;
|
let img = fs::read(path).await?;
|
||||||
let img = tokio::task::spawn_blocking(move || -> Result<DynamicImage> {
|
let img = tokio::task::spawn_blocking(move || -> Result<DynamicImage> {
|
||||||
let img = image::load_from_memory(&img)?;
|
let img = image::load_from_memory(&img)?;
|
||||||
Ok(if img.width() > w || img.height() > h {
|
Ok(if img.width() > max_width || img.height() > max_height {
|
||||||
img.resize(w, h, FilterType::Triangle)
|
img.resize(max_width, max_height, FilterType::Triangle)
|
||||||
} else {
|
} else {
|
||||||
img
|
img
|
||||||
})
|
})
|
||||||
|
|
@ -34,13 +37,15 @@ impl Image {
|
||||||
let cache = cache.as_ref().to_owned();
|
let cache = cache.as_ref().to_owned();
|
||||||
let result = tokio::task::spawn_blocking(move || {
|
let result = tokio::task::spawn_blocking(move || {
|
||||||
let img = image::load_from_memory(&img)?;
|
let img = image::load_from_memory(&img)?;
|
||||||
let (w, h) = (PREVIEW.max_width, PREVIEW.max_height);
|
let (max_width, max_height) = (PREVIEW.max_width, PREVIEW.max_height);
|
||||||
|
|
||||||
if img.width() <= w && img.height() <= h {
|
if img.width() <= max_width && img.height() <= max_height {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
img.resize(w, h, FilterType::Triangle).save_with_format(cache, ImageFormat::Jpeg)?;
|
img
|
||||||
|
.resize(max_width, max_height, FilterType::Triangle)
|
||||||
|
.save_with_format(cache, ImageFormat::Jpeg)?;
|
||||||
Ok(true)
|
Ok(true)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
use std::{io::{stdout, BufWriter, Write}, path::Path};
|
use std::{
|
||||||
|
io::{stdout, BufWriter, Write},
|
||||||
|
path::Path,
|
||||||
|
};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use base64::{engine::general_purpose, Engine};
|
use base64::{engine::general_purpose, Engine};
|
||||||
|
|
@ -33,8 +36,6 @@ impl Iterm2 {
|
||||||
|
|
||||||
async fn encode(img: DynamicImage) -> Result<Vec<u8>> {
|
async fn encode(img: DynamicImage) -> Result<Vec<u8>> {
|
||||||
tokio::task::spawn_blocking(move || {
|
tokio::task::spawn_blocking(move || {
|
||||||
let size = (img.width(), img.height());
|
|
||||||
|
|
||||||
let mut jpg = vec![];
|
let mut jpg = vec![];
|
||||||
JpegEncoder::new_with_quality(&mut jpg, 75).encode_image(&img)?;
|
JpegEncoder::new_with_quality(&mut jpg, 75).encode_image(&img)?;
|
||||||
|
|
||||||
|
|
@ -44,8 +45,8 @@ impl Iterm2 {
|
||||||
"{}]1337;File=inline=1;size={};width={}px;height={}px;doNotMoveCursor=1:{}\x07{}",
|
"{}]1337;File=inline=1;size={};width={}px;height={}px;doNotMoveCursor=1:{}\x07{}",
|
||||||
START,
|
START,
|
||||||
jpg.len(),
|
jpg.len(),
|
||||||
size.0,
|
img.width(),
|
||||||
size.1,
|
img.height(),
|
||||||
general_purpose::STANDARD.encode(&jpg),
|
general_purpose::STANDARD.encode(&jpg),
|
||||||
CLOSE
|
CLOSE
|
||||||
)?;
|
)?;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
use std::{io::{stdout, Write}, path::Path};
|
use std::{
|
||||||
|
io::{stdout, Write},
|
||||||
|
path::Path,
|
||||||
|
};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use base64::{engine::general_purpose, Engine};
|
use base64::{engine::general_purpose, Engine};
|
||||||
|
|
@ -29,7 +32,7 @@ impl Kitty {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn encode(img: DynamicImage) -> Result<Vec<u8>> {
|
async fn encode(img: DynamicImage) -> Result<Vec<u8>> {
|
||||||
fn output(raw: &[u8], format: u8, size: (u32, u32)) -> Result<Vec<u8>> {
|
fn output(raw: &[u8], format: u8, (width, height): (u32, u32)) -> Result<Vec<u8>> {
|
||||||
let b64 = general_purpose::STANDARD.encode(raw).chars().collect::<Vec<_>>();
|
let b64 = general_purpose::STANDARD.encode(raw).chars().collect::<Vec<_>>();
|
||||||
|
|
||||||
let mut it = b64.chunks(4096).peekable();
|
let mut it = b64.chunks(4096).peekable();
|
||||||
|
|
@ -40,8 +43,8 @@ impl Kitty {
|
||||||
"{}_Ga=T,f={},s={},v={},m={};{}{}\\{}",
|
"{}_Ga=T,f={},s={},v={},m={};{}{}\\{}",
|
||||||
START,
|
START,
|
||||||
format,
|
format,
|
||||||
size.0,
|
width,
|
||||||
size.1,
|
height,
|
||||||
it.peek().is_some() as u8,
|
it.peek().is_some() as u8,
|
||||||
first.iter().collect::<String>(),
|
first.iter().collect::<String>(),
|
||||||
ESCAPE,
|
ESCAPE,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
use ratatui::{layout::{self, Constraint}, prelude::{Buffer, Direction, Rect}, style::{Color, Style, Stylize}, widgets::{List, ListItem, Widget}};
|
use ratatui::{
|
||||||
|
layout::{self, Constraint},
|
||||||
|
prelude::{Buffer, Direction, Rect},
|
||||||
|
style::{Color, Style, Stylize},
|
||||||
|
widgets::{List, ListItem, Widget},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::context::Ctx;
|
use crate::context::Ctx;
|
||||||
|
|
||||||
|
|
@ -7,7 +12,9 @@ pub(super) struct Bindings<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Bindings<'a> {
|
impl<'a> Bindings<'a> {
|
||||||
pub(super) fn new(cx: &'a Ctx) -> Self { Self { cx } }
|
pub(super) fn new(cx: &'a Ctx) -> Self {
|
||||||
|
Self { cx }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for Bindings<'_> {
|
impl Widget for Bindings<'_> {
|
||||||
|
|
@ -17,17 +24,20 @@ impl Widget for Bindings<'_> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let col1 = bindings
|
// The First Column
|
||||||
|
let keys = bindings
|
||||||
.iter()
|
.iter()
|
||||||
.map(|c| ListItem::new(c.on()).style(Style::new().fg(Color::Yellow)))
|
.map(|c| ListItem::new(c.on()).style(Style::new().fg(Color::Yellow)))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let col2 = bindings
|
// The Second Column
|
||||||
|
let commands = bindings
|
||||||
.iter()
|
.iter()
|
||||||
.map(|c| ListItem::new(c.exec()).style(Style::new().fg(Color::Cyan)))
|
.map(|c| ListItem::new(c.exec()).style(Style::new().fg(Color::Cyan)))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let col3 = bindings
|
// The Third Column
|
||||||
|
let desc = bindings
|
||||||
.iter()
|
.iter()
|
||||||
.map(|c| ListItem::new(if let Some(ref desc) = c.desc { desc } else { "-" }))
|
.map(|c| ListItem::new(if let Some(ref desc) = c.desc { desc } else { "-" }))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
@ -43,8 +53,8 @@ impl Widget for Bindings<'_> {
|
||||||
Style::new().bg(Color::Black).bold(),
|
Style::new().bg(Color::Black).bold(),
|
||||||
);
|
);
|
||||||
|
|
||||||
List::new(col1).render(chunks[0], buf);
|
for (i, col) in [keys, commands, desc].into_iter().enumerate() {
|
||||||
List::new(col2).render(chunks[1], buf);
|
List::new(col).render(chunks[i], buf);
|
||||||
List::new(col3).render(chunks[2], buf);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
141
app/src/manager/#folder.rs#
Normal file
141
app/src/manager/#folder.rs#
Normal file
|
|
@ -0,0 +1,141 @@
|
||||||
|
use core::files::File;
|
||||||
|
|
||||||
|
use config::{MANAGER, THEME};
|
||||||
|
use ratatui::{
|
||||||
|
buffer::Buffer,
|
||||||
|
layout::Rect,
|
||||||
|
style::{Color, Modifier, Style},
|
||||||
|
text::{Line, Span},
|
||||||
|
widgets::{List, ListItem, Widget},
|
||||||
|
};
|
||||||
|
use shared::short_path;
|
||||||
|
|
||||||
|
use crate::Ctx;
|
||||||
|
|
||||||
|
pub(super) struct Folder<'a> {
|
||||||
|
cx: &'a Ctx,
|
||||||
|
folder: &'a core::manager::Folder,
|
||||||
|
is_preview: bool,
|
||||||
|
is_selection: bool,
|
||||||
|
is_find: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Folder<'a> {
|
||||||
|
pub(super) fn new(cx: &'a Ctx, folder: &'a core::manager::Folder) -> Self {
|
||||||
|
Self { cx, folder, is_preview: false, is_selection: false, is_find: false }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(super) fn with_preview(mut self, state: bool) -> Self {
|
||||||
|
self.is_preview = state;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(super) fn with_selection(mut self, state: bool) -> Self {
|
||||||
|
self.is_selection = state;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(super) fn with_find(mut self, state: bool) -> Self {
|
||||||
|
self.is_find = state;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Folder<'a> {
|
||||||
|
#[inline]
|
||||||
|
fn icon(file: &File) -> &'static str {
|
||||||
|
THEME
|
||||||
|
.icons
|
||||||
|
.iter()
|
||||||
|
.find(|x| x.name.match_path(file.url(), Some(file.is_dir())))
|
||||||
|
.map(|x| x.display.as_ref())
|
||||||
|
.unwrap_or("")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn file_style(&self, file: &File) -> Style {
|
||||||
|
let mimetype = &self.cx.manager.mimetype;
|
||||||
|
THEME
|
||||||
|
.filetypes
|
||||||
|
.iter()
|
||||||
|
.find(|x| x.matches(file.url(), mimetype.get(file.url()), file.is_dir()))
|
||||||
|
.map(|x| x.style.get())
|
||||||
|
.unwrap_or_else(Style::new)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Widget for Folder<'a> {
|
||||||
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
|
let active = self.cx.manager.active();
|
||||||
|
let mode = active.mode();
|
||||||
|
|
||||||
|
let window = if self.is_preview {
|
||||||
|
self.folder.window_for(active.preview().skip())
|
||||||
|
} else {
|
||||||
|
self.folder.window()
|
||||||
|
};
|
||||||
|
|
||||||
|
let items: Vec<_> = window
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, file)| {
|
||||||
|
let is_selected = self.folder.files.is_selected(file.url());
|
||||||
|
if (!self.is_selection && is_selected)
|
||||||
|
|| (self.is_selection && mode.pending(self.folder.offset() + i, is_selected))
|
||||||
|
{
|
||||||
|
buf.set_style(
|
||||||
|
Rect { x: area.x.saturating_sub(1), y: i as u16 + 1, width: 1, height: 1 },
|
||||||
|
if self.is_selection {
|
||||||
|
THEME.marker.selecting.get()
|
||||||
|
} else {
|
||||||
|
THEME.marker.selected.get()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let hovered = matches!(self.folder.hovered, Some(ref hover) if hover.url() == file.url());
|
||||||
|
|
||||||
|
let style = match (self.is_preview, hovered) {
|
||||||
|
(true, true) => THEME.preview.hovered.get(),
|
||||||
|
(_, true) => THEME.selection.hovered.get(),
|
||||||
|
_ => self.file_style(file),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut spans = Vec::with_capacity(10);
|
||||||
|
|
||||||
|
spans.push(Span::raw(format!(" {} ", Self::icon(file))));
|
||||||
|
spans.push(Span::raw(short_path(file.url(), &self.folder.cwd)));
|
||||||
|
|
||||||
|
if let Some(link_to) = file.link_to() {
|
||||||
|
if MANAGER.show_symlink {
|
||||||
|
spans.push(Span::raw(format!(" -> {}", link_to.display())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(idx) = active
|
||||||
|
.finder()
|
||||||
|
.filter(|&finder| hovered && self.is_find && finder.has_matched())
|
||||||
|
.and_then(|finder| finder.matched_idx(file.url()))
|
||||||
|
{
|
||||||
|
let len = active.finder().unwrap().matched().len();
|
||||||
|
let style = Style::new().fg(Color::Rgb(255, 255, 50)).add_modifier(Modifier::ITALIC);
|
||||||
|
spans.push(Span::styled(
|
||||||
|
format!(
|
||||||
|
" [{}/{}]",
|
||||||
|
if idx > 99 { ">99".to_string() } else { (idx + 1).to_string() },
|
||||||
|
if len > 99 { ">99".to_string() } else { len.to_string() }
|
||||||
|
),
|
||||||
|
style,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
ListItem::new(Line::from(spans)).style(style)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
List::new(items).render(area, buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,13 @@
|
||||||
use core::files::File;
|
use core::files::File;
|
||||||
|
|
||||||
use config::{MANAGER, THEME};
|
use config::{MANAGER, THEME};
|
||||||
use ratatui::{buffer::Buffer, layout::Rect, style::{Color, Modifier, Style}, text::{Line, Span}, widgets::{List, ListItem, Widget}};
|
use ratatui::{
|
||||||
|
buffer::Buffer,
|
||||||
|
layout::Rect,
|
||||||
|
style::{Color, Modifier, Style},
|
||||||
|
text::{Line, Span},
|
||||||
|
widgets::{List, ListItem, Widget},
|
||||||
|
};
|
||||||
use shared::short_path;
|
use shared::short_path;
|
||||||
|
|
||||||
use crate::Ctx;
|
use crate::Ctx;
|
||||||
|
|
@ -75,8 +81,8 @@ impl<'a> Widget for Folder<'a> {
|
||||||
let items: Vec<_> = window
|
let items: Vec<_> = window
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, f)| {
|
.map(|(i, file)| {
|
||||||
let is_selected = self.folder.files.is_selected(f.url());
|
let is_selected = self.folder.files.is_selected(file.url());
|
||||||
if (!self.is_selection && is_selected)
|
if (!self.is_selection && is_selected)
|
||||||
|| (self.is_selection && mode.pending(self.folder.offset() + i, is_selected))
|
|| (self.is_selection && mode.pending(self.folder.offset() + i, is_selected))
|
||||||
{
|
{
|
||||||
|
|
@ -90,21 +96,21 @@ impl<'a> Widget for Folder<'a> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let hovered = matches!(self.folder.hovered, Some(ref h) if h.url() == f.url());
|
let hovered =
|
||||||
let style = if self.is_preview && hovered {
|
matches!(self.folder.hovered, Some(ref hover) if hover.url() == file.url());
|
||||||
THEME.preview.hovered.get()
|
|
||||||
} else if hovered {
|
let style = match (self.is_preview, hovered) {
|
||||||
THEME.selection.hovered.get()
|
(true, true) => THEME.preview.hovered.get(),
|
||||||
} else {
|
(_, true) => THEME.selection.hovered.get(),
|
||||||
self.file_style(f)
|
_ => self.file_style(file),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut spans = Vec::with_capacity(10);
|
let mut spans = Vec::with_capacity(10);
|
||||||
|
|
||||||
spans.push(Span::raw(format!(" {} ", Self::icon(f))));
|
spans.push(Span::raw(format!(" {} ", Self::icon(file))));
|
||||||
spans.push(Span::raw(short_path(f.url(), &self.folder.cwd)));
|
spans.push(Span::raw(short_path(file.url(), &self.folder.cwd)));
|
||||||
|
|
||||||
if let Some(link_to) = f.link_to() {
|
if let Some(link_to) = file.link_to() {
|
||||||
if MANAGER.show_symlink {
|
if MANAGER.show_symlink {
|
||||||
spans.push(Span::raw(format!(" -> {}", link_to.display())));
|
spans.push(Span::raw(format!(" -> {}", link_to.display())));
|
||||||
}
|
}
|
||||||
|
|
@ -112,8 +118,8 @@ impl<'a> Widget for Folder<'a> {
|
||||||
|
|
||||||
if let Some(idx) = active
|
if let Some(idx) = active
|
||||||
.finder()
|
.finder()
|
||||||
.filter(|&f| hovered && self.is_find && f.has_matched())
|
.filter(|&finder| hovered && self.is_find && finder.has_matched())
|
||||||
.and_then(|finder| finder.matched_idx(f.url()))
|
.and_then(|finder| finder.matched_idx(file.url()))
|
||||||
{
|
{
|
||||||
let len = active.finder().unwrap().matched().len();
|
let len = active.finder().unwrap().matched().len();
|
||||||
let style = Style::new().fg(Color::Rgb(255, 255, 50)).add_modifier(Modifier::ITALIC);
|
let style = Style::new().fg(Color::Rgb(255, 255, 50)).add_modifier(Modifier::ITALIC);
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ 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) {
|
||||||
let manager = &self.cx.manager;
|
let manager = &self.cx.manager;
|
||||||
let Some(hovered) = manager.hovered().map(|h| h.url()) else {
|
let Some(hovered) = manager.hovered().map(|hovered| hovered.url()) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue