mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
..
This commit is contained in:
parent
a3971fc4f5
commit
46c54f6bad
26 changed files with 175 additions and 139 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
use core::{emit, files::FilesSorter, input::InputMode, manager::FinderCase};
|
use core::{emit, files::FilesSorter, input::InputMode, manager::FinderCase, Ctx};
|
||||||
|
|
||||||
use config::{keymap::{Control, Exec, Key, KeymapLayer}, manager::SortBy, KEYMAP};
|
use config::{keymap::{Control, Exec, Key, KeymapLayer}, manager::SortBy, KEYMAP};
|
||||||
use shared::{optional_bool, Url};
|
use shared::{optional_bool, Url};
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ impl<'a> Widget for Layout<'a> {
|
||||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
let chunks = layout::Layout::new()
|
let chunks = layout::Layout::new()
|
||||||
.direction(Direction::Horizontal)
|
.direction(Direction::Horizontal)
|
||||||
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
|
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
|
||||||
.split(area);
|
.split(area);
|
||||||
|
|
||||||
let cwd = &self.cx.manager.current().cwd;
|
let cwd = &self.cx.manager.current().cwd;
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ impl<'a> Widget for Layout<'a> {
|
||||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
let chunks = layout::Layout::new()
|
let chunks = layout::Layout::new()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.constraints([Constraint::Min(0), Constraint::Length(1)].as_ref())
|
.constraints([Constraint::Min(0), Constraint::Length(1)])
|
||||||
.split(area);
|
.split(area);
|
||||||
|
|
||||||
Clear.render(area, buf);
|
Clear.render(area, buf);
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,7 @@
|
||||||
use core::Ctx;
|
|
||||||
|
|
||||||
use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget};
|
use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
pub(super) struct Folder<'a> {
|
pub(super) struct Folder {
|
||||||
cx: &'a Ctx,
|
|
||||||
kind: FolderKind,
|
kind: FolderKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -14,11 +11,11 @@ pub(super) enum FolderKind {
|
||||||
Preview = 2,
|
Preview = 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Folder<'a> {
|
impl Folder {
|
||||||
pub(super) fn new(cx: &'a Ctx, kind: FolderKind) -> Self { Self { cx, kind } }
|
pub(super) fn new(kind: FolderKind) -> Self { Self { kind } }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Folder<'a> {
|
impl Folder {
|
||||||
// fn highlighted_item<'b>(&'b self, file: &'b File) -> Vec<Span> {
|
// fn highlighted_item<'b>(&'b self, file: &'b File) -> Vec<Span> {
|
||||||
// let short = short_path(file.url(), &self.folder.cwd);
|
// let short = short_path(file.url(), &self.folder.cwd);
|
||||||
//
|
//
|
||||||
|
|
@ -39,7 +36,7 @@ impl<'a> Folder<'a> {
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Widget for Folder<'a> {
|
impl Widget for Folder {
|
||||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
let x = plugin::Folder { kind: self.kind as u8 }.render(area);
|
let x = plugin::Folder { kind: self.kind as u8 }.render(area);
|
||||||
if x.is_err() {
|
if x.is_err() {
|
||||||
|
|
@ -55,21 +52,6 @@ impl<'a> Widget for Folder<'a> {
|
||||||
// .iter()
|
// .iter()
|
||||||
// .enumerate()
|
// .enumerate()
|
||||||
// .map(|(i, f)| {
|
// .map(|(i, f)| {
|
||||||
// let is_selected = self.folder.files.is_selected(f.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()
|
|
||||||
// },
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// if let Some(idx) = active
|
// if let Some(idx) = active
|
||||||
// .finder()
|
// .finder()
|
||||||
// .filter(|&f| hovered && self.is_find && f.has_matched())
|
// .filter(|&f| hovered && self.is_find && f.has_matched())
|
||||||
|
|
|
||||||
|
|
@ -20,25 +20,22 @@ impl<'a> Widget for Layout<'a> {
|
||||||
|
|
||||||
let chunks = layout::Layout::new()
|
let chunks = layout::Layout::new()
|
||||||
.direction(Direction::Horizontal)
|
.direction(Direction::Horizontal)
|
||||||
.constraints(
|
.constraints([
|
||||||
[
|
Constraint::Ratio(layout.parent, layout.all),
|
||||||
Constraint::Ratio(layout.parent, layout.all),
|
Constraint::Ratio(layout.current, layout.all),
|
||||||
Constraint::Ratio(layout.current, layout.all),
|
Constraint::Ratio(layout.preview, layout.all),
|
||||||
Constraint::Ratio(layout.preview, layout.all),
|
])
|
||||||
]
|
|
||||||
.as_ref(),
|
|
||||||
)
|
|
||||||
.split(area);
|
.split(area);
|
||||||
|
|
||||||
// Parent
|
// Parent
|
||||||
let block = Block::new().borders(Borders::RIGHT).padding(Padding::new(1, 0, 0, 0));
|
let block = Block::new().borders(Borders::RIGHT).padding(Padding::new(1, 0, 0, 0));
|
||||||
if manager.parent().is_some() {
|
if manager.parent().is_some() {
|
||||||
Folder::new(self.cx, FolderKind::Parent).render(block.inner(chunks[0]), buf);
|
Folder::new(FolderKind::Parent).render(block.inner(chunks[0]), buf);
|
||||||
}
|
}
|
||||||
block.render(chunks[0], buf);
|
block.render(chunks[0], buf);
|
||||||
|
|
||||||
// Current
|
// Current
|
||||||
Folder::new(self.cx, FolderKind::Current).render(chunks[1], buf);
|
Folder::new(FolderKind::Current).render(chunks[1], buf);
|
||||||
|
|
||||||
// Preview
|
// Preview
|
||||||
let block = Block::new().borders(Borders::LEFT).padding(Padding::new(0, 1, 0, 0));
|
let block = Block::new().borders(Borders::LEFT).padding(Padding::new(0, 1, 0, 0));
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ impl<'a> Widget for Preview<'a> {
|
||||||
|
|
||||||
match &preview.lock.as_ref().unwrap().data {
|
match &preview.lock.as_ref().unwrap().data {
|
||||||
PreviewData::Folder => {
|
PreviewData::Folder => {
|
||||||
Folder::new(self.cx, FolderKind::Preview).render(area, buf);
|
Folder::new(FolderKind::Preview).render(area, buf);
|
||||||
}
|
}
|
||||||
PreviewData::Text(s) => {
|
PreviewData::Text(s) => {
|
||||||
let p = Paragraph::new(s.as_bytes().into_text().unwrap());
|
let p = Paragraph::new(s.as_bytes().into_text().unwrap());
|
||||||
|
|
|
||||||
|
|
@ -17,12 +17,12 @@ impl<'a> Widget for Root<'a> {
|
||||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
let chunks = Layout::new()
|
let chunks = Layout::new()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.constraints([Constraint::Length(1), Constraint::Min(0), Constraint::Length(1)].as_ref())
|
.constraints([Constraint::Length(1), Constraint::Min(0), Constraint::Length(1)])
|
||||||
.split(area);
|
.split(area);
|
||||||
|
|
||||||
header::Layout::new(self.cx).render(chunks[0], buf);
|
header::Layout::new(self.cx).render(chunks[0], buf);
|
||||||
manager::Layout::new(self.cx).render(chunks[1], buf);
|
manager::Layout::new(self.cx).render(chunks[1], buf);
|
||||||
status::Layout::new(self.cx).render(chunks[2], buf);
|
status::Layout.render(chunks[2], buf);
|
||||||
|
|
||||||
if self.cx.tasks.visible {
|
if self.cx.tasks.visible {
|
||||||
tasks::Layout::new(self.cx).render(area, buf);
|
tasks::Layout::new(self.cx).render(area, buf);
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,9 @@
|
||||||
use core::Ctx;
|
|
||||||
|
|
||||||
use ratatui::{buffer::Buffer, prelude::Rect, widgets::Widget};
|
use ratatui::{buffer::Buffer, prelude::Rect, widgets::Widget};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
pub(crate) struct Layout<'a> {
|
pub(crate) struct Layout;
|
||||||
cx: &'a Ctx,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Layout<'a> {
|
impl Widget for Layout {
|
||||||
pub(crate) fn new(cx: &'a Ctx) -> Self { Self { cx } }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Widget for Layout<'a> {
|
|
||||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
let x = plugin::Status::render(area);
|
let x = plugin::Status::render(area);
|
||||||
if x.is_err() {
|
if x.is_err() {
|
||||||
|
|
|
||||||
|
|
@ -14,29 +14,21 @@ impl<'a> Layout<'a> {
|
||||||
pub(super) fn area(area: Rect) -> Rect {
|
pub(super) fn area(area: Rect) -> Rect {
|
||||||
let chunk = layout::Layout::new()
|
let chunk = layout::Layout::new()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.constraints(
|
.constraints([
|
||||||
[
|
Constraint::Percentage((100 - TASKS_PERCENT) / 2),
|
||||||
Constraint::Percentage((100 - TASKS_PERCENT) / 2),
|
Constraint::Percentage(TASKS_PERCENT),
|
||||||
Constraint::Percentage(TASKS_PERCENT),
|
Constraint::Percentage((100 - TASKS_PERCENT) / 2),
|
||||||
Constraint::Percentage((100 - TASKS_PERCENT) / 2),
|
])
|
||||||
]
|
|
||||||
.as_ref(),
|
|
||||||
)
|
|
||||||
.split(area)[1];
|
.split(area)[1];
|
||||||
|
|
||||||
let chunk = layout::Layout::new()
|
layout::Layout::new()
|
||||||
.direction(Direction::Horizontal)
|
.direction(Direction::Horizontal)
|
||||||
.constraints(
|
.constraints([
|
||||||
[
|
Constraint::Percentage((100 - TASKS_PERCENT) / 2),
|
||||||
Constraint::Percentage((100 - TASKS_PERCENT) / 2),
|
Constraint::Percentage(TASKS_PERCENT),
|
||||||
Constraint::Percentage(TASKS_PERCENT),
|
Constraint::Percentage((100 - TASKS_PERCENT) / 2),
|
||||||
Constraint::Percentage((100 - TASKS_PERCENT) / 2),
|
])
|
||||||
]
|
.split(chunk)[1]
|
||||||
.as_ref(),
|
|
||||||
)
|
|
||||||
.split(chunk)[1];
|
|
||||||
|
|
||||||
chunk
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,9 +35,7 @@ impl Widget for Which<'_> {
|
||||||
|
|
||||||
let chunks = layout::Layout::new()
|
let chunks = layout::Layout::new()
|
||||||
.direction(Direction::Horizontal)
|
.direction(Direction::Horizontal)
|
||||||
.constraints(
|
.constraints([Constraint::Ratio(1, 3), Constraint::Ratio(1, 3), Constraint::Ratio(1, 3)])
|
||||||
[Constraint::Ratio(1, 3), Constraint::Ratio(1, 3), Constraint::Ratio(1, 3)].as_ref(),
|
|
||||||
)
|
|
||||||
.split(area);
|
.split(area);
|
||||||
|
|
||||||
Clear.render(area, buf);
|
Clear.render(area, buf);
|
||||||
|
|
|
||||||
|
|
@ -23,12 +23,13 @@ permissions_r = { fg = "#F3D398" }
|
||||||
permissions_w = { fg = "#FA7F94" }
|
permissions_w = { fg = "#FA7F94" }
|
||||||
permissions_x = { fg = "#7AD9E5" }
|
permissions_x = { fg = "#7AD9E5" }
|
||||||
|
|
||||||
[selection]
|
[files]
|
||||||
hovered = { fg = "#1E2031", bg = "#80AEFA" }
|
hovered = { fg = "#1E2031", bg = "#80AEFA" }
|
||||||
|
|
||||||
[marker]
|
[marker]
|
||||||
selecting = { fg = "#97DC8D", bg = "#97DC8D" }
|
selected = { fg = "#97DC8D", bg = "#97DC8D" }
|
||||||
selected = { fg = "#F3D398", bg = "#F3D398" }
|
copied = { fg = "#F3D398", bg = "#F3D398" }
|
||||||
|
cut = { fg = "#FF84A9", bg = "#FF84A9" }
|
||||||
|
|
||||||
[preview]
|
[preview]
|
||||||
hovered = { underline = true }
|
hovered = { underline = true }
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,13 @@ use serde::{Deserialize, Serialize};
|
||||||
use super::Style;
|
use super::Style;
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
pub struct Selection {
|
pub struct Files {
|
||||||
pub hovered: Style,
|
pub hovered: Style,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
pub struct Marker {
|
pub struct Marker {
|
||||||
pub selecting: Style,
|
pub selected: Style,
|
||||||
pub selected: Style,
|
pub copied: Style,
|
||||||
|
pub cut: Style,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
|
||||||
use shared::expand_path;
|
use shared::expand_path;
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
use super::{Filetype, Icon, Marker, Selection, Status, Style};
|
use super::{Files, Filetype, Icon, Marker, Status, Style};
|
||||||
use crate::{validation::check_validation, MERGED_THEME};
|
use crate::{validation::check_validation, MERGED_THEME};
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Validate)]
|
#[derive(Deserialize, Serialize, Validate)]
|
||||||
|
|
@ -25,7 +25,7 @@ pub struct Preview {
|
||||||
pub struct Theme {
|
pub struct Theme {
|
||||||
pub tab: Tab,
|
pub tab: Tab,
|
||||||
pub status: Status,
|
pub status: Status,
|
||||||
pub selection: Selection,
|
pub files: Files,
|
||||||
pub marker: Marker,
|
pub marker: Marker,
|
||||||
pub preview: Preview,
|
pub preview: Preview,
|
||||||
#[serde(rename = "filetype", deserialize_with = "Filetype::deserialize", skip_serializing)]
|
#[serde(rename = "filetype", deserialize_with = "Filetype::deserialize", skip_serializing)]
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,9 @@ use super::{Backstack, Finder, FinderCase, Folder, Mode, Preview, PreviewLock};
|
||||||
use crate::{emit, external::{self, FzfOpt, ZoxideOpt}, files::{File, FilesOp, FilesSorter}, input::InputOpt, Event, Step, BLOCKER};
|
use crate::{emit, external::{self, FzfOpt, ZoxideOpt}, files::{File, FilesOp, FilesSorter}, input::InputOpt, Event, Step, BLOCKER};
|
||||||
|
|
||||||
pub struct Tab {
|
pub struct Tab {
|
||||||
pub(super) mode: Mode,
|
pub mode: Mode,
|
||||||
pub(super) current: Folder,
|
pub current: Folder,
|
||||||
pub(super) parent: Option<Folder>,
|
pub parent: Option<Folder>,
|
||||||
|
|
||||||
pub(super) backstack: Backstack<Url>,
|
pub(super) backstack: Backstack<Url>,
|
||||||
pub(super) history: BTreeMap<Url, Folder>,
|
pub(super) history: BTreeMap<Url, Folder>,
|
||||||
|
|
@ -455,14 +455,10 @@ impl Tab {
|
||||||
impl Tab {
|
impl Tab {
|
||||||
// --- Mode
|
// --- Mode
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn mode(&self) -> &Mode { &self.mode }
|
pub fn in_selecting(&self) -> bool { self.mode.is_visual() || self.current.files.has_selected() }
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn in_selecting(&self) -> bool {
|
|
||||||
self.mode().is_visual() || self.current.files.has_selected()
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Current
|
// --- Current
|
||||||
|
// TODO: remove this
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn name(&self) -> &str {
|
pub fn name(&self) -> &str {
|
||||||
self
|
self
|
||||||
|
|
@ -475,10 +471,9 @@ impl Tab {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn selected(&self) -> Vec<&File> {
|
pub fn selected(&self) -> Vec<&File> {
|
||||||
let mode = self.mode();
|
let pending = self.mode.visual().map(|(_, p)| Cow::Borrowed(p)).unwrap_or_default();
|
||||||
let pending = mode.visual().map(|(_, p)| Cow::Borrowed(p)).unwrap_or_default();
|
let selected = self.current.files.selected(&pending, self.mode.is_unset());
|
||||||
|
|
||||||
let selected = self.current.files.selected(&pending, mode.is_unset());
|
|
||||||
if selected.is_empty() {
|
if selected.is_empty() {
|
||||||
self.current.hovered.as_ref().map(|h| vec![h]).unwrap_or_default()
|
self.current.hovered.as_ref().map(|h| vec![h]).unwrap_or_default()
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"language":"en","version":"0.2","flagWords":[],"words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","️ Überzug","️ Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort"]}
|
{"flagWords":[],"words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","️ Überzug","️ Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit"],"language":"en","version":"0.2"}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,6 @@ shared = { path = "../shared" }
|
||||||
|
|
||||||
# External dependencies
|
# External dependencies
|
||||||
anyhow = "^1"
|
anyhow = "^1"
|
||||||
mlua = { version = "^0", features = [ "lua54", "vendored", "serialize" ] }
|
mlua = { version = "^0", features = [ "luajit52", "vendored", "serialize" ] }
|
||||||
tracing = "^0"
|
tracing = "^0"
|
||||||
ratatui = "^0"
|
ratatui = "^0"
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,11 @@ Folder = {
|
||||||
|
|
||||||
function Folder:by_kind(kind)
|
function Folder:by_kind(kind)
|
||||||
if kind == self.Kind.Parent then
|
if kind == self.Kind.Parent then
|
||||||
return cx.manager.parent
|
return cx.active.parent
|
||||||
elseif kind == self.Kind.Current then
|
elseif kind == self.Kind.Current then
|
||||||
return cx.manager.current
|
return cx.active.current
|
||||||
elseif kind == self.Kind.Preview then
|
elseif kind == self.Kind.Preview then
|
||||||
return cx.manager.preview.folder
|
return cx.active.preview.folder
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -20,6 +20,36 @@ function Folder:window(kind) return (self:by_kind(kind) or {}).window end
|
||||||
|
|
||||||
function Folder:hovered(kind) return (self:by_kind(kind) or {}).hovered end
|
function Folder:hovered(kind) return (self:by_kind(kind) or {}).hovered end
|
||||||
|
|
||||||
|
function Folder:markers(area, markers)
|
||||||
|
if #markers == 0 then
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
|
||||||
|
local elements = {}
|
||||||
|
local append = function(last)
|
||||||
|
local rect = ui.Rect {
|
||||||
|
x = area.x - 1,
|
||||||
|
y = area.y + last[1] - 1,
|
||||||
|
w = 1,
|
||||||
|
h = 1 + last[2] - last[1],
|
||||||
|
}
|
||||||
|
elements[#elements + 1] = ui.Paragraph(rect, {}):style(THEME.marker.selected)
|
||||||
|
end
|
||||||
|
|
||||||
|
local last = { markers[1][1], markers[1][1], markers[1][2] } -- start, end, type
|
||||||
|
for _, m in ipairs(markers) do
|
||||||
|
if m[1] - last[2] > 1 or last[3] ~= m[2] then
|
||||||
|
append(last)
|
||||||
|
last = { m[1], m[1], m[2] }
|
||||||
|
else
|
||||||
|
last[2] = m[1]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
append(last)
|
||||||
|
return elements
|
||||||
|
end
|
||||||
|
|
||||||
function Folder:parent(area)
|
function Folder:parent(area)
|
||||||
local window = self:window(self.Kind.Parent)
|
local window = self:window(self.Kind.Parent)
|
||||||
if window == nil then
|
if window == nil then
|
||||||
|
|
@ -28,10 +58,10 @@ function Folder:parent(area)
|
||||||
|
|
||||||
local hovered = (self:hovered(self.Kind.Parent) or {}).url
|
local hovered = (self:hovered(self.Kind.Parent) or {}).url
|
||||||
local lines = {}
|
local lines = {}
|
||||||
for _, f in pairs(window) do
|
for _, f in ipairs(window) do
|
||||||
local line = ui.Line { ui.Span(" " .. f:icon() .. " " .. f.name .. " ") }
|
local line = ui.Line { ui.Span(" " .. f:icon() .. " " .. f.name .. " ") }
|
||||||
if f.url == hovered then
|
if f.url == hovered then
|
||||||
line = line:style(THEME.selection.hovered)
|
line = line:style(THEME.files.hovered)
|
||||||
else
|
else
|
||||||
line = line:style(f:style())
|
line = line:style(f:style())
|
||||||
end
|
end
|
||||||
|
|
@ -44,8 +74,9 @@ end
|
||||||
|
|
||||||
function Folder:current(area)
|
function Folder:current(area)
|
||||||
local hovered = (self:hovered(self.Kind.Current) or {}).url
|
local hovered = (self:hovered(self.Kind.Current) or {}).url
|
||||||
|
local markers = {}
|
||||||
local lines = {}
|
local lines = {}
|
||||||
for _, f in pairs(self:window(self.Kind.Current)) do
|
for i, f in ipairs(self:window(self.Kind.Current)) do
|
||||||
local name = f.name
|
local name = f.name
|
||||||
|
|
||||||
-- Show symlink target
|
-- Show symlink target
|
||||||
|
|
@ -59,15 +90,19 @@ function Folder:current(area)
|
||||||
-- Highlight hovered file
|
-- Highlight hovered file
|
||||||
local line = ui.Line { ui.Span(" " .. f:icon() .. " " .. name .. " ") }
|
local line = ui.Line { ui.Span(" " .. f:icon() .. " " .. name .. " ") }
|
||||||
if f.url == hovered then
|
if f.url == hovered then
|
||||||
line = line:style(THEME.selection.hovered)
|
line = line:style(THEME.files.hovered)
|
||||||
else
|
else
|
||||||
line = line:style(f:style())
|
line = line:style(f:style())
|
||||||
end
|
end
|
||||||
|
|
||||||
lines[#lines + 1] = line
|
lines[#lines + 1] = line
|
||||||
|
|
||||||
|
-- Mark selected/yanked files
|
||||||
|
if f:selected() then
|
||||||
|
markers[#markers + 1] = { i, 1 }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return { ui.Paragraph(area, lines) }
|
return { ui.Paragraph(area, lines), table.unpack(self:markers(area, markers)) }
|
||||||
end
|
end
|
||||||
|
|
||||||
function Folder:preview(area)
|
function Folder:preview(area)
|
||||||
|
|
@ -78,7 +113,7 @@ function Folder:preview(area)
|
||||||
|
|
||||||
local hovered = (self:hovered(self.Kind.Preview) or {}).url
|
local hovered = (self:hovered(self.Kind.Preview) or {}).url
|
||||||
local lines = {}
|
local lines = {}
|
||||||
for _, f in pairs(window) do
|
for _, f in ipairs(window) do
|
||||||
local line = ui.Line { ui.Span(" " .. f:icon() .. " " .. f.name .. " ") }
|
local line = ui.Line { ui.Span(" " .. f:icon() .. " " .. f.name .. " ") }
|
||||||
if f.url == hovered then
|
if f.url == hovered then
|
||||||
line = line:style(THEME.preview.hovered)
|
line = line:style(THEME.preview.hovered)
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
Status = {}
|
Status = {}
|
||||||
|
|
||||||
function Status.style()
|
function Status.style()
|
||||||
if cx.manager.mode.is_select then
|
if cx.active.mode.is_select then
|
||||||
return THEME.status.mode_select
|
return THEME.status.mode_select
|
||||||
elseif cx.manager.mode.is_unset then
|
elseif cx.active.mode.is_unset then
|
||||||
return THEME.status.mode_unset
|
return THEME.status.mode_unset
|
||||||
else
|
else
|
||||||
return THEME.status.mode_normal
|
return THEME.status.mode_normal
|
||||||
|
|
@ -11,7 +11,7 @@ function Status.style()
|
||||||
end
|
end
|
||||||
|
|
||||||
function Status:mode()
|
function Status:mode()
|
||||||
local mode = tostring(cx.manager.mode):upper()
|
local mode = tostring(cx.active.mode):upper()
|
||||||
if mode == "UNSET" then
|
if mode == "UNSET" then
|
||||||
mode = "UN-SET"
|
mode = "UN-SET"
|
||||||
end
|
end
|
||||||
|
|
@ -24,7 +24,7 @@ function Status:mode()
|
||||||
end
|
end
|
||||||
|
|
||||||
function Status:size()
|
function Status:size()
|
||||||
local h = cx.manager.current.hovered
|
local h = cx.active.current.hovered
|
||||||
if h == nil then
|
if h == nil then
|
||||||
return ui.Span("")
|
return ui.Span("")
|
||||||
end
|
end
|
||||||
|
|
@ -37,7 +37,7 @@ function Status:size()
|
||||||
end
|
end
|
||||||
|
|
||||||
function Status:name()
|
function Status:name()
|
||||||
local h = cx.manager.current.hovered
|
local h = cx.active.current.hovered
|
||||||
if h == nil then
|
if h == nil then
|
||||||
return ui.Span("")
|
return ui.Span("")
|
||||||
end
|
end
|
||||||
|
|
@ -46,7 +46,7 @@ function Status:name()
|
||||||
end
|
end
|
||||||
|
|
||||||
function Status:permissions()
|
function Status:permissions()
|
||||||
local h = cx.manager.current.hovered
|
local h = cx.active.current.hovered
|
||||||
if h == nil or h.permissions == nil then
|
if h == nil or h.permissions == nil then
|
||||||
return ui.Span("")
|
return ui.Span("")
|
||||||
end
|
end
|
||||||
|
|
@ -69,8 +69,8 @@ end
|
||||||
|
|
||||||
function Status:percentage()
|
function Status:percentage()
|
||||||
local percent = 0
|
local percent = 0
|
||||||
local cursor = cx.manager.current.cursor
|
local cursor = cx.active.current.cursor
|
||||||
local length = #cx.manager.current.files
|
local length = #cx.active.current.files
|
||||||
if cursor ~= 0 and length ~= 0 then
|
if cursor ~= 0 and length ~= 0 then
|
||||||
percent = math.floor((cursor + 1) * 100 / length)
|
percent = math.floor((cursor + 1) * 100 / length)
|
||||||
end
|
end
|
||||||
|
|
@ -89,8 +89,8 @@ function Status:percentage()
|
||||||
end
|
end
|
||||||
|
|
||||||
function Status:position()
|
function Status:position()
|
||||||
local cursor = cx.manager.current.cursor
|
local cursor = cx.active.current.cursor
|
||||||
local length = #cx.manager.current.files
|
local length = #cx.active.current.files
|
||||||
|
|
||||||
local style = self.style()
|
local style = self.style()
|
||||||
return ui.Line {
|
return ui.Line {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
pub fn init() -> mlua::Result<()> {
|
pub fn init() -> mlua::Result<()> {
|
||||||
super::manager::Manager::init()?;
|
super::tab::Tab::init()?;
|
||||||
super::tasks::Tasks::init()?;
|
super::tasks::Tasks::init()?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
#![allow(clippy::module_inception)]
|
#![allow(clippy::module_inception)]
|
||||||
|
|
||||||
mod bindings;
|
mod bindings;
|
||||||
mod manager;
|
|
||||||
mod shared;
|
mod shared;
|
||||||
|
mod tab;
|
||||||
mod tasks;
|
mod tasks;
|
||||||
|
|
||||||
pub use bindings::*;
|
pub use bindings::*;
|
||||||
pub use manager::*;
|
|
||||||
pub use shared::*;
|
pub use shared::*;
|
||||||
|
pub use tab::*;
|
||||||
pub use tasks::*;
|
pub use tasks::*;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
use core::Ctx;
|
||||||
|
|
||||||
use config::{MANAGER, THEME};
|
use config::{MANAGER, THEME};
|
||||||
use mlua::{AnyUserData, Function, IntoLua, MetaMethod, UserData, UserDataFields, UserDataMethods, Value};
|
use mlua::{AnyUserData, Function, IntoLua, MetaMethod, UserData, UserDataFields, UserDataMethods, Value};
|
||||||
|
|
||||||
|
|
@ -20,14 +22,16 @@ impl UserData for File {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Manager<'a, 'b> {
|
pub struct Tab<'a, 'b> {
|
||||||
scope: &'b mlua::Scope<'a, 'a>,
|
scope: &'b mlua::Scope<'a, 'a>,
|
||||||
inner: &'a core::manager::Manager,
|
|
||||||
|
cx: &'a core::Ctx,
|
||||||
|
inner: &'a core::manager::Tab,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b> Manager<'a, 'b> {
|
impl<'a, 'b> Tab<'a, 'b> {
|
||||||
pub(crate) fn init() -> mlua::Result<()> {
|
pub(crate) fn init() -> mlua::Result<()> {
|
||||||
LUA.register_userdata_type::<core::manager::Manager>(|reg| {
|
LUA.register_userdata_type::<core::manager::Tab>(|reg| {
|
||||||
reg.add_field_function_get("mode", |_, me| me.named_user_value::<AnyUserData>("mode"));
|
reg.add_field_function_get("mode", |_, me| me.named_user_value::<AnyUserData>("mode"));
|
||||||
reg.add_field_function_get("parent", |_, me| me.named_user_value::<Value>("parent"));
|
reg.add_field_function_get("parent", |_, me| me.named_user_value::<Value>("parent"));
|
||||||
reg.add_field_function_get("current", |_, me| me.named_user_value::<AnyUserData>("current"));
|
reg.add_field_function_get("current", |_, me| me.named_user_value::<AnyUserData>("current"));
|
||||||
|
|
@ -85,6 +89,9 @@ impl<'a, 'b> Manager<'a, 'b> {
|
||||||
reg.add_function("style", |_, me: AnyUserData| {
|
reg.add_function("style", |_, me: AnyUserData| {
|
||||||
me.named_user_value::<Function>("style")?.call::<_, Style>(())
|
me.named_user_value::<Function>("style")?.call::<_, Style>(())
|
||||||
});
|
});
|
||||||
|
reg.add_function("selected", |_, me: AnyUserData| {
|
||||||
|
me.named_user_value::<Function>("selected")?.call::<_, bool>(me)
|
||||||
|
});
|
||||||
|
|
||||||
reg.add_field_method_get("url", |_, me| Ok(Url::from(me.url())));
|
reg.add_field_method_get("url", |_, me| Ok(Url::from(me.url())));
|
||||||
reg.add_field_method_get("length", |_, me| Ok(me.length()));
|
reg.add_field_method_get("length", |_, me| Ok(me.length()));
|
||||||
|
|
@ -105,19 +112,24 @@ impl<'a, 'b> Manager<'a, 'b> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn new(scope: &'b mlua::Scope<'a, 'a>, inner: &'a core::manager::Manager) -> Self {
|
pub(crate) fn new(
|
||||||
Self { scope, inner }
|
scope: &'b mlua::Scope<'a, 'a>,
|
||||||
|
|
||||||
|
cx: &'a Ctx,
|
||||||
|
inner: &'a core::manager::Tab,
|
||||||
|
) -> Self {
|
||||||
|
Self { scope, cx, inner }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn make(&self) -> mlua::Result<AnyUserData<'a>> {
|
pub(crate) fn make(&self) -> mlua::Result<AnyUserData<'a>> {
|
||||||
let ud = self.scope.create_any_userdata_ref(self.inner)?;
|
let ud = self.scope.create_any_userdata_ref(self.inner)?;
|
||||||
|
ud.set_named_user_value("mode", self.scope.create_any_userdata_ref(&self.inner.mode)?)?;
|
||||||
ud.set_named_user_value(
|
ud.set_named_user_value(
|
||||||
"mode",
|
"parent",
|
||||||
self.scope.create_any_userdata_ref(self.inner.active().mode())?,
|
self.inner.parent.as_ref().and_then(|p| self.folder(p, None).ok()),
|
||||||
)?;
|
)?;
|
||||||
ud.set_named_user_value("parent", self.inner.parent().and_then(|p| self.folder(p, None).ok()))?;
|
ud.set_named_user_value("current", self.folder(&self.inner.current, None)?)?;
|
||||||
ud.set_named_user_value("current", self.folder(self.inner.current(), None)?)?;
|
ud.set_named_user_value("preview", self.preview(self.inner)?)?;
|
||||||
ud.set_named_user_value("preview", self.preview(self.inner.active())?)?;
|
|
||||||
|
|
||||||
Ok(ud)
|
Ok(ud)
|
||||||
}
|
}
|
||||||
|
|
@ -137,11 +149,16 @@ impl<'a, 'b> Manager<'a, 'b> {
|
||||||
.iter()
|
.iter()
|
||||||
.skip(window.0)
|
.skip(window.0)
|
||||||
.take(window.1)
|
.take(window.1)
|
||||||
.filter_map(|f| self.file(f).ok())
|
.enumerate()
|
||||||
|
.filter_map(|(i, f)| self.file(i, f, inner).ok())
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
)?;
|
)?;
|
||||||
ud.set_named_user_value("files", self.files(&inner.files)?)?;
|
ud.set_named_user_value("files", self.files(&inner.files)?)?;
|
||||||
ud.set_named_user_value("hovered", inner.hovered.as_ref().and_then(|h| self.file(h).ok()))?;
|
// TODO: remove this
|
||||||
|
ud.set_named_user_value(
|
||||||
|
"hovered",
|
||||||
|
inner.hovered.as_ref().and_then(|h| self.file(999, h, inner).ok()),
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(ud)
|
Ok(ud)
|
||||||
}
|
}
|
||||||
|
|
@ -150,8 +167,15 @@ impl<'a, 'b> Manager<'a, 'b> {
|
||||||
self.scope.create_any_userdata_ref(inner)
|
self.scope.create_any_userdata_ref(inner)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn file(&self, inner: &'a core::files::File) -> mlua::Result<AnyUserData<'a>> {
|
fn file(
|
||||||
|
&self,
|
||||||
|
idx: usize,
|
||||||
|
inner: &'a core::files::File,
|
||||||
|
folder: &'a core::manager::Folder,
|
||||||
|
) -> mlua::Result<AnyUserData<'a>> {
|
||||||
let ud = self.scope.create_any_userdata_ref(inner)?;
|
let ud = self.scope.create_any_userdata_ref(inner)?;
|
||||||
|
ud.set_named_user_value("idx", idx)?;
|
||||||
|
|
||||||
ud.set_named_user_value(
|
ud.set_named_user_value(
|
||||||
"icon",
|
"icon",
|
||||||
self.scope.create_function(|_, ()| {
|
self.scope.create_function(|_, ()| {
|
||||||
|
|
@ -168,7 +192,7 @@ impl<'a, 'b> Manager<'a, 'b> {
|
||||||
ud.set_named_user_value(
|
ud.set_named_user_value(
|
||||||
"style",
|
"style",
|
||||||
self.scope.create_function(|_, ()| {
|
self.scope.create_function(|_, ()| {
|
||||||
let mime = self.inner.mimetype.get(inner.url());
|
let mime = self.cx.manager.mimetype.get(inner.url());
|
||||||
Ok(
|
Ok(
|
||||||
THEME
|
THEME
|
||||||
.filetypes
|
.filetypes
|
||||||
|
|
@ -179,6 +203,20 @@ impl<'a, 'b> Manager<'a, 'b> {
|
||||||
})?,
|
})?,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
ud.set_named_user_value(
|
||||||
|
"selected",
|
||||||
|
self.scope.create_function(|_, me: AnyUserData| {
|
||||||
|
let is_visual = self.inner.mode.is_visual();
|
||||||
|
let selected = folder.files.is_selected(inner.url());
|
||||||
|
Ok(if !is_visual {
|
||||||
|
selected
|
||||||
|
} else {
|
||||||
|
let idx: usize = me.named_user_value("idx")?;
|
||||||
|
self.inner.mode.pending(folder.offset() + idx, selected)
|
||||||
|
})
|
||||||
|
})?,
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(ud)
|
Ok(ud)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,7 +58,7 @@ impl UserData for Line {
|
||||||
Value::Table(tbl) => me.0.patch_style(Style::from(tbl).0),
|
Value::Table(tbl) => me.0.patch_style(Style::from(tbl).0),
|
||||||
Value::UserData(ud) => me.0.patch_style(ud.borrow::<Style>()?.0),
|
Value::UserData(ud) => me.0.patch_style(ud.borrow::<Style>()?.0),
|
||||||
_ => return Err(mlua::Error::external("expected a Style or Table or nil")),
|
_ => return Err(mlua::Error::external("expected a Style or Table or nil")),
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
Ok(ud)
|
Ok(ud)
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -56,8 +56,16 @@ impl<'lua> FromLua<'lua> for Paragraph {
|
||||||
|
|
||||||
impl UserData for Paragraph {
|
impl UserData for Paragraph {
|
||||||
fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) {
|
fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||||
methods.add_function("style", |_, (ud, style): (AnyUserData, Style)| {
|
methods.add_function("style", |_, (ud, value): (AnyUserData, Value)| {
|
||||||
ud.borrow_mut::<Self>()?.style = Some(style.0);
|
{
|
||||||
|
let mut me = ud.borrow_mut::<Self>()?;
|
||||||
|
match value {
|
||||||
|
Value::Nil => me.style = None,
|
||||||
|
Value::Table(tbl) => me.style = Some(Style::from(tbl).0),
|
||||||
|
Value::UserData(ud) => me.style = Some(ud.borrow::<Style>()?.0),
|
||||||
|
_ => return Err(mlua::Error::external("expected a Style or Table or nil")),
|
||||||
|
}
|
||||||
|
}
|
||||||
Ok(ud)
|
Ok(ud)
|
||||||
});
|
});
|
||||||
methods.add_function("align", |_, (ud, align): (AnyUserData, u8)| {
|
methods.add_function("align", |_, (ud, align): (AnyUserData, u8)| {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use config::theme::Color;
|
use config::theme::Color;
|
||||||
use mlua::{AnyUserData, FromLua, Lua, Table, UserData, UserDataMethods, Value};
|
use mlua::{AnyUserData, FromLua, Lua, Table, UserData, UserDataMethods, Value};
|
||||||
use tracing::info;
|
|
||||||
|
|
||||||
use crate::{GLOBALS, LUA};
|
use crate::{GLOBALS, LUA};
|
||||||
|
|
||||||
|
|
@ -51,7 +50,6 @@ impl UserData for Style {
|
||||||
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
|
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||||
methods.add_function("fg", |_, (ud, color): (AnyUserData, String)| {
|
methods.add_function("fg", |_, (ud, color): (AnyUserData, String)| {
|
||||||
ud.borrow_mut::<Self>()?.0.fg = Color::try_from(color).ok().map(Into::into);
|
ud.borrow_mut::<Self>()?.0.fg = Color::try_from(color).ok().map(Into::into);
|
||||||
info!("fg: {:?}", ud.borrow::<Self>()?.0.fg);
|
|
||||||
Ok(ud)
|
Ok(ud)
|
||||||
});
|
});
|
||||||
methods.add_function("bg", |_, (ud, color): (AnyUserData, String)| {
|
methods.add_function("bg", |_, (ud, color): (AnyUserData, String)| {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use crate::{bindings, GLOBALS, LUA};
|
||||||
pub fn scope<F: FnOnce()>(cx: &Ctx, f: F) {
|
pub fn scope<F: FnOnce()>(cx: &Ctx, f: F) {
|
||||||
let _ = LUA.scope(|scope| {
|
let _ = LUA.scope(|scope| {
|
||||||
let tbl = LUA.create_table()?;
|
let tbl = LUA.create_table()?;
|
||||||
tbl.set("manager", bindings::Manager::new(scope, &cx.manager).make()?)?;
|
tbl.set("active", bindings::Tab::new(scope, cx, cx.manager.active()).make()?)?;
|
||||||
tbl.set("tasks", bindings::Tasks::make(scope, &cx.tasks)?)?;
|
tbl.set("tasks", bindings::Tasks::make(scope, &cx.tasks)?)?;
|
||||||
GLOBALS.set("cx", tbl)?;
|
GLOBALS.set("cx", tbl)?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,6 @@ pub fn copy_with_progress(from: &Path, to: &Path) -> mpsc::Receiver<Result<u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert a file mode to a string representation
|
// Convert a file mode to a string representation
|
||||||
#[cfg(unix)]
|
|
||||||
#[allow(clippy::collapsible_else_if)]
|
#[allow(clippy::collapsible_else_if)]
|
||||||
pub fn permissions(permissions: Permissions) -> Option<String> {
|
pub fn permissions(permissions: Permissions) -> Option<String> {
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue