mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
feat: make visual mode support wraparound scrolling
This commit is contained in:
parent
6e0aaee822
commit
12be774d94
33 changed files with 253 additions and 117 deletions
|
|
@ -21,7 +21,7 @@ impl Actor for Show {
|
||||||
|
|
||||||
let area = cx.mgr.area(opt.position).padding(cx.input.padding());
|
let area = cx.mgr.area(opt.position).padding(cx.input.padding());
|
||||||
let input = &mut cx.input;
|
let input = &mut cx.input;
|
||||||
input.main.id = opt.id.clone();
|
input.main.name = opt.name.clone();
|
||||||
input.main.title = opt.title.clone();
|
input.main.title = opt.title.clone();
|
||||||
input.main.position = opt.position;
|
input.main.position = opt.position;
|
||||||
input.main.visible = true;
|
input.main.visible = true;
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,11 @@ impl UserData for File {
|
||||||
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
||||||
yazi_binding::impl_file_methods!(methods);
|
yazi_binding::impl_file_methods!(methods);
|
||||||
|
|
||||||
methods.add_method("icon", |_, me, ()| {
|
methods.add_method("icon", |lua, me, ()| {
|
||||||
|
yazi_binding::deprecate!(
|
||||||
|
lua,
|
||||||
|
"{}: `File:icon()` is deprecated, use `th.icon:match(file)` instead"
|
||||||
|
);
|
||||||
// TODO: use a cache
|
// TODO: use a cache
|
||||||
Ok(yazi_config::THEME.icon.matches(me, me.is_hovered()))
|
Ok(yazi_config::THEME.icon.matches(me, me.is_hovered()))
|
||||||
});
|
});
|
||||||
|
|
@ -100,16 +104,16 @@ impl UserData for File {
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
methods.add_method("is_marked", |_, me, ()| {
|
methods.add_method("is_marked", |_, me, ()| {
|
||||||
use yazi_core::tab::Mode::*;
|
let Some(visual) = me.tab.mode.visual() else {
|
||||||
if !me.tab.mode.is_visual() || me.folder.url != me.tab.current.url {
|
return Ok(0u8);
|
||||||
|
};
|
||||||
|
if !visual.contains(me.idx, me.tab.current.cursor, me.folder.entries.len()) {
|
||||||
return Ok(0u8);
|
return Ok(0u8);
|
||||||
}
|
}
|
||||||
|
if me.folder.url != me.tab.current.url {
|
||||||
Ok(match &me.tab.mode {
|
return Ok(0u8);
|
||||||
Select(_, indices) if indices.contains(&me.idx) => 1u8,
|
}
|
||||||
Unset(_, indices) if indices.contains(&me.idx) => 2u8,
|
Ok(if me.tab.mode.is_select() { 1u8 } else { 2u8 })
|
||||||
_ => 0u8,
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
methods.add_method("is_selected", |_, me, ()| Ok(me.tab.selected.contains(&me.url)));
|
methods.add_method("is_selected", |_, me, ()| Ok(me.tab.selected.contains(&me.url)));
|
||||||
methods.add_method("found", |lua, me, ()| {
|
methods.add_method("found", |lua, me, ()| {
|
||||||
|
|
@ -144,6 +148,6 @@ impl UserData for File {
|
||||||
inventory::submit! {
|
inventory::submit! {
|
||||||
FileInventory {
|
FileInventory {
|
||||||
register: |_| {},
|
register: |_| {},
|
||||||
from_lua: |ud| Ok(ud.borrow::<File>()?.clone()),
|
borrow: |ud, f| f(&*ud.borrow::<File>()?),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
use mlua::{AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMethods};
|
use mlua::{AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMethods};
|
||||||
|
use yazi_binding::deprecate;
|
||||||
|
|
||||||
use super::{Lives, PtrCell};
|
use super::{Lives, PtrCell};
|
||||||
|
|
||||||
|
|
@ -22,9 +23,13 @@ impl Mode {
|
||||||
|
|
||||||
impl UserData for Mode {
|
impl UserData for Mode {
|
||||||
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
||||||
|
fields.add_field_method_get("is_normal", |_, me| Ok(me.is_normal()));
|
||||||
fields.add_field_method_get("is_select", |_, me| Ok(me.is_select()));
|
fields.add_field_method_get("is_select", |_, me| Ok(me.is_select()));
|
||||||
fields.add_field_method_get("is_unset", |_, me| Ok(me.is_unset()));
|
fields.add_field_method_get("is_unset", |_, me| Ok(me.is_unset()));
|
||||||
fields.add_field_method_get("is_visual", |_, me| Ok(me.is_visual()));
|
fields.add_field_method_get("is_visual", |lua, me| {
|
||||||
|
deprecate!(lua, "{}: `mode.is_visual` is deprecated, use `not mode.is_normal` instead.");
|
||||||
|
Ok(!me.is_normal())
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ impl Actor for Arrow {
|
||||||
|
|
||||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||||
let tab = cx.tab_mut();
|
let tab = cx.tab_mut();
|
||||||
|
let old = tab.current.cursor;
|
||||||
if !tab.current.arrow(form.step) {
|
if !tab.current.arrow(form.step) {
|
||||||
succ!();
|
succ!();
|
||||||
}
|
}
|
||||||
|
|
@ -22,9 +23,8 @@ impl Actor for Arrow {
|
||||||
tab.current.retrace();
|
tab.current.retrace();
|
||||||
|
|
||||||
// Visual selection
|
// Visual selection
|
||||||
if let Some((start, items)) = tab.mode.visual_mut() {
|
if let Some(visual) = tab.mode.visual_mut() {
|
||||||
let end = tab.current.cursor;
|
visual.arrow(form.step, old, tab.current.cursor);
|
||||||
*items = (start.min(end)..=end.max(start)).collect();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
act!(mgr:hover, cx)?;
|
act!(mgr:hover, cx)?;
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,9 @@ impl Actor for EscapeVisual {
|
||||||
let tab = cx.tab_mut();
|
let tab = cx.tab_mut();
|
||||||
|
|
||||||
let select = tab.mode.is_select();
|
let select = tab.mode.is_select();
|
||||||
let Some((_, indices)) = tab.mode.take_visual() else { succ!(false) };
|
let Some(indices) = tab.mode.take_visual(tab.current.cursor, tab.current.entries.len()) else {
|
||||||
|
succ!(false)
|
||||||
|
};
|
||||||
|
|
||||||
render!();
|
render!();
|
||||||
let files: Vec<_> = indices.into_iter().filter_map(|i| tab.current.entries.get(i)).collect();
|
let files: Vec<_> = indices.into_iter().filter_map(|i| tab.current.entries.get(i)).collect();
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
use std::collections::BTreeSet;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use yazi_core::tab::Mode;
|
use yazi_core::tab::{Mode, Visual};
|
||||||
use yazi_macro::{render, succ};
|
use yazi_macro::{render, succ};
|
||||||
use yazi_parser::mgr::VisualModeForm;
|
use yazi_parser::mgr::VisualModeForm;
|
||||||
use yazi_shared::data::Data;
|
use yazi_shared::data::Data;
|
||||||
|
|
@ -20,9 +18,9 @@ impl Actor for VisualMode {
|
||||||
|
|
||||||
let idx = tab.current.cursor;
|
let idx = tab.current.cursor;
|
||||||
if form.unset {
|
if form.unset {
|
||||||
tab.mode = Mode::Unset(idx, BTreeSet::from([idx]));
|
tab.mode = Mode::Unset(Visual::new(idx));
|
||||||
} else {
|
} else {
|
||||||
tab.mode = Mode::Select(idx, BTreeSet::from([idx]));
|
tab.mode = Mode::Select(Visual::new(idx));
|
||||||
};
|
};
|
||||||
|
|
||||||
succ!(render!());
|
succ!(render!());
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,17 @@ keymap = [
|
||||||
{ on = "<C-c>", run = "close", desc = "Close the current tab, or quit if it's last" },
|
{ on = "<C-c>", run = "close", desc = "Close the current tab, or quit if it's last" },
|
||||||
{ on = "<C-z>", run = "suspend", desc = "Suspend the process" },
|
{ on = "<C-z>", run = "suspend", desc = "Suspend the process" },
|
||||||
|
|
||||||
# Hopping
|
# Hop around
|
||||||
{ on = "k", run = "arrow prev", desc = "Previous file" },
|
{ on = "k", run = "arrow prev", desc = "Previous file" },
|
||||||
{ on = "j", run = "arrow next", desc = "Next file" },
|
{ on = "j", run = "arrow next", desc = "Next file" },
|
||||||
|
|
||||||
{ on = "<Up>", run = "arrow prev", desc = "Previous file" },
|
{ on = "<Up>", run = "arrow prev", desc = "Previous file" },
|
||||||
{ on = "<Down>", run = "arrow next", desc = "Next file" },
|
{ on = "<Down>", run = "arrow next", desc = "Next file" },
|
||||||
|
|
||||||
|
{ on = [ "g", "g" ], run = "arrow top", desc = "Go to top" },
|
||||||
|
{ on = "G", run = "arrow bot", desc = "Go to bottom" },
|
||||||
|
{ on = "<Home>", run = "arrow top", desc = "Go to top" },
|
||||||
|
{ on = "<End>", run = "arrow bot", desc = "Go to bottom" },
|
||||||
|
|
||||||
{ on = "<C-u>", run = "arrow -50%", desc = "Move cursor up half page" },
|
{ on = "<C-u>", run = "arrow -50%", desc = "Move cursor up half page" },
|
||||||
{ on = "<C-d>", run = "arrow 50%", desc = "Move cursor down half page" },
|
{ on = "<C-d>", run = "arrow 50%", desc = "Move cursor down half page" },
|
||||||
{ on = "<C-b>", run = "arrow -100%", desc = "Move cursor up one page" },
|
{ on = "<C-b>", run = "arrow -100%", desc = "Move cursor up one page" },
|
||||||
|
|
@ -30,9 +34,6 @@ keymap = [
|
||||||
{ on = "<PageUp>", run = "arrow -100%", desc = "Move cursor up one page" },
|
{ on = "<PageUp>", run = "arrow -100%", desc = "Move cursor up one page" },
|
||||||
{ on = "<PageDown>", run = "arrow 100%", desc = "Move cursor down one page" },
|
{ on = "<PageDown>", run = "arrow 100%", desc = "Move cursor down one page" },
|
||||||
|
|
||||||
{ on = [ "g", "g" ], run = "arrow top", desc = "Go to top" },
|
|
||||||
{ on = "G", run = "arrow bot", desc = "Go to bottom" },
|
|
||||||
|
|
||||||
# Navigation
|
# Navigation
|
||||||
{ on = "h", run = "leave", desc = "Back to the parent directory" },
|
{ on = "h", run = "leave", desc = "Back to the parent directory" },
|
||||||
{ on = "l", run = "enter", desc = "Enter the child directory" },
|
{ on = "l", run = "enter", desc = "Enter the child directory" },
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,15 @@ use crate::THEME;
|
||||||
inventory::submit! {
|
inventory::submit! {
|
||||||
FileInventory {
|
FileInventory {
|
||||||
register: |registry| {
|
register: |registry| {
|
||||||
registry.add_method("icon", |_, me, ()| {
|
registry.add_method("icon", |lua, me, ()| {
|
||||||
|
yazi_binding::deprecate!(
|
||||||
|
lua,
|
||||||
|
"{}: `File:icon()` is deprecated, use `th.icon:match(file)` instead"
|
||||||
|
);
|
||||||
// TODO: use a cache
|
// TODO: use a cache
|
||||||
Ok(THEME.icon.matches(me, false))
|
Ok(THEME.icon.matches(me, false))
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
from_lua: |_| Err(mlua::Error::UserDataTypeMismatch),
|
borrow: |_, _| Err(mlua::Error::UserDataTypeMismatch),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ impl TryFrom<Table> for OpenRuleMatcher<'static> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
rules: YAZI.open.load_full(),
|
rules: YAZI.open.load_full(),
|
||||||
id,
|
id,
|
||||||
file: file.map(|f| f.clone().into()),
|
file: file.map(TryInto::try_into).transpose()?,
|
||||||
mime: mime.map(Into::into),
|
mime: mime.map(Into::into),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@ impl TryFrom<Table> for FetcherMatcher<'static> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
fetchers: YAZI.plugin.fetchers.load_full(),
|
fetchers: YAZI.plugin.fetchers.load_full(),
|
||||||
id,
|
id,
|
||||||
file: file.map(|f| f.clone().into()),
|
file: file.map(TryInto::try_into).transpose()?,
|
||||||
mime: mime.map(Into::into),
|
mime: mime.map(Into::into),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ impl TryFrom<Table> for PreloaderMatcher<'static> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
preloaders: YAZI.plugin.preloaders.load_full(),
|
preloaders: YAZI.plugin.preloaders.load_full(),
|
||||||
id,
|
id,
|
||||||
file: file.map(|f| f.clone().into()),
|
file: file.map(TryInto::try_into).transpose()?,
|
||||||
mime: mime.map(Into::into),
|
mime: mime.map(Into::into),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ impl TryFrom<Table> for PreviewerMatcher<'static> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
previewers: YAZI.plugin.previewers.load_full(),
|
previewers: YAZI.plugin.previewers.load_full(),
|
||||||
id,
|
id,
|
||||||
file: file.map(|f| f.clone().into()),
|
file: file.map(TryInto::try_into).transpose()?,
|
||||||
mime: mime.map(Into::into),
|
mime: mime.map(Into::into),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ impl TryFrom<Table> for SpotterMatcher<'static> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
spotters: YAZI.plugin.spotters.load_full(),
|
spotters: YAZI.plugin.spotters.load_full(),
|
||||||
id,
|
id,
|
||||||
file: file.map(|f| f.clone().into()),
|
file: file.map(TryInto::try_into).transpose()?,
|
||||||
mime: mime.map(Into::into),
|
mime: mime.map(Into::into),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ pub struct Input {
|
||||||
impl Input {
|
impl Input {
|
||||||
pub fn cd(&self, cwd: Url) -> InputOpt {
|
pub fn cd(&self, cwd: Url) -> InputOpt {
|
||||||
InputOpt {
|
InputOpt {
|
||||||
id: "cd".to_owned(),
|
name: "cd".to_owned(),
|
||||||
title: self.cd_title.clone(),
|
title: self.cd_title.clone(),
|
||||||
value: if cwd.kind().is_local() { String::new() } else { EncodeScheme(cwd).to_string() },
|
value: if cwd.kind().is_local() { String::new() } else { EncodeScheme(cwd).to_string() },
|
||||||
position: Position::new(self.cd_origin, self.cd_offset),
|
position: Position::new(self.cd_origin, self.cd_offset),
|
||||||
|
|
@ -58,7 +58,7 @@ impl Input {
|
||||||
|
|
||||||
pub fn create(&self, dir: bool) -> InputOpt {
|
pub fn create(&self, dir: bool) -> InputOpt {
|
||||||
InputOpt {
|
InputOpt {
|
||||||
id: "create".to_owned(),
|
name: format!("create-{}", if dir { "dir" } else { "file" }),
|
||||||
title: self.create_title[dir as usize].clone(),
|
title: self.create_title[dir as usize].clone(),
|
||||||
position: Position::new(self.create_origin, self.create_offset),
|
position: Position::new(self.create_origin, self.create_offset),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
@ -67,7 +67,7 @@ impl Input {
|
||||||
|
|
||||||
pub fn rename(&self, is_dir: bool) -> InputOpt {
|
pub fn rename(&self, is_dir: bool) -> InputOpt {
|
||||||
InputOpt {
|
InputOpt {
|
||||||
id: format!("rename-{}", if is_dir { "dir" } else { "file" }),
|
name: format!("rename-{}", if is_dir { "dir" } else { "file" }),
|
||||||
title: self.rename_title.clone(),
|
title: self.rename_title.clone(),
|
||||||
position: Position::new(self.rename_origin, self.rename_offset),
|
position: Position::new(self.rename_origin, self.rename_offset),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
@ -76,7 +76,7 @@ impl Input {
|
||||||
|
|
||||||
pub fn filter(&self) -> InputOpt {
|
pub fn filter(&self) -> InputOpt {
|
||||||
InputOpt {
|
InputOpt {
|
||||||
id: "filter".to_owned(),
|
name: "filter".to_owned(),
|
||||||
title: self.filter_title.clone(),
|
title: self.filter_title.clone(),
|
||||||
position: Position::new(self.filter_origin, self.filter_offset),
|
position: Position::new(self.filter_origin, self.filter_offset),
|
||||||
realtime: true,
|
realtime: true,
|
||||||
|
|
@ -86,7 +86,7 @@ impl Input {
|
||||||
|
|
||||||
pub fn find(&self, prev: bool) -> InputOpt {
|
pub fn find(&self, prev: bool) -> InputOpt {
|
||||||
InputOpt {
|
InputOpt {
|
||||||
id: "find".to_owned(),
|
name: "find".to_owned(),
|
||||||
title: self.find_title[prev as usize].clone(),
|
title: self.find_title[prev as usize].clone(),
|
||||||
position: Position::new(self.find_origin, self.find_offset),
|
position: Position::new(self.find_origin, self.find_offset),
|
||||||
realtime: true,
|
realtime: true,
|
||||||
|
|
@ -96,7 +96,7 @@ impl Input {
|
||||||
|
|
||||||
pub fn search(&self, name: &str) -> InputOpt {
|
pub fn search(&self, name: &str) -> InputOpt {
|
||||||
InputOpt {
|
InputOpt {
|
||||||
id: "search".to_owned(),
|
name: "search".to_owned(),
|
||||||
title: self.search_title.replace("{n}", name),
|
title: self.search_title.replace("{n}", name),
|
||||||
position: Position::new(self.search_origin, self.search_offset),
|
position: Position::new(self.search_origin, self.search_offset),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
@ -105,7 +105,7 @@ impl Input {
|
||||||
|
|
||||||
pub fn shell(&self, block: bool) -> InputOpt {
|
pub fn shell(&self, block: bool) -> InputOpt {
|
||||||
InputOpt {
|
InputOpt {
|
||||||
id: "shell".to_owned(),
|
name: "shell".to_owned(),
|
||||||
title: self.shell_title[block as usize].clone(),
|
title: self.shell_title[block as usize].clone(),
|
||||||
position: Position::new(self.shell_origin, self.shell_offset),
|
position: Position::new(self.shell_origin, self.shell_offset),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
@ -114,7 +114,7 @@ impl Input {
|
||||||
|
|
||||||
pub fn tab_rename(&self) -> InputOpt {
|
pub fn tab_rename(&self) -> InputOpt {
|
||||||
InputOpt {
|
InputOpt {
|
||||||
id: "tab-rename".to_owned(),
|
name: "tab-rename".to_owned(),
|
||||||
title: "Rename tab:".to_owned(),
|
title: "Rename tab:".to_owned(),
|
||||||
position: Position::new(Origin::TopCenter, Offset {
|
position: Position::new(Origin::TopCenter, Offset {
|
||||||
x: 0,
|
x: 0,
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ impl Input {
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct InputMain {
|
pub struct InputMain {
|
||||||
inner: yazi_widgets::input::Input,
|
inner: yazi_widgets::input::Input,
|
||||||
pub id: String,
|
pub name: String,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub position: Position,
|
pub position: Position,
|
||||||
pub visible: bool,
|
pub visible: bool,
|
||||||
|
|
|
||||||
|
|
@ -22,14 +22,16 @@ impl TryFrom<Table> for SpotLock {
|
||||||
|
|
||||||
fn try_from(t: Table) -> Result<Self, Self::Error> {
|
fn try_from(t: Table) -> Result<Self, Self::Error> {
|
||||||
let file: FileRef = t.raw_get("file")?;
|
let file: FileRef = t.raw_get("file")?;
|
||||||
Ok(Self {
|
file.borrow(|f| {
|
||||||
url: file.url_owned(),
|
Ok(Self {
|
||||||
cha: file.cha,
|
url: f.url_owned(),
|
||||||
mime: t.raw_get("mime")?,
|
cha: f.cha,
|
||||||
|
mime: t.raw_get("mime")?,
|
||||||
|
|
||||||
id: t.raw_get("id")?,
|
id: t.raw_get("id")?,
|
||||||
skip: t.raw_get("skip")?,
|
skip: t.raw_get("skip")?,
|
||||||
data: Default::default(),
|
data: Default::default(),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
yazi_macro::mod_flat!(backstack finder folder history mode preference preview preview_lock selected tab);
|
yazi_macro::mod_flat!(backstack finder folder history visual mode preference preview preview_lock selected tab);
|
||||||
|
|
|
||||||
|
|
@ -1,41 +1,43 @@
|
||||||
use std::{collections::BTreeSet, fmt::Display, mem};
|
use std::{fmt::{self, Display}, mem};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
use strum::EnumIs;
|
||||||
|
|
||||||
|
use super::Visual;
|
||||||
|
use crate::tab::VisualIndices;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, EnumIs, Eq, PartialEq)]
|
||||||
pub enum Mode {
|
pub enum Mode {
|
||||||
#[default]
|
#[default]
|
||||||
Normal,
|
Normal,
|
||||||
Select(usize, BTreeSet<usize>),
|
Select(Visual),
|
||||||
Unset(usize, BTreeSet<usize>),
|
Unset(Visual),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mode {
|
impl Mode {
|
||||||
pub fn visual_mut(&mut self) -> Option<(usize, &mut BTreeSet<usize>)> {
|
pub fn visual(&self) -> Option<Visual> {
|
||||||
match self {
|
match self {
|
||||||
Self::Normal => None,
|
Self::Normal => None,
|
||||||
Self::Select(start, indices) => Some((*start, indices)),
|
Self::Select(visual) | Self::Unset(visual) => Some(*visual),
|
||||||
Self::Unset(start, indices) => Some((*start, indices)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn take_visual(&mut self) -> Option<(usize, BTreeSet<usize>)> {
|
pub fn visual_mut(&mut self) -> Option<&mut Visual> {
|
||||||
|
match self {
|
||||||
|
Self::Normal => None,
|
||||||
|
Self::Select(visual) | Self::Unset(visual) => Some(visual),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn take_visual(&mut self, end: usize, len: usize) -> Option<VisualIndices> {
|
||||||
match mem::take(self) {
|
match mem::take(self) {
|
||||||
Self::Normal => None,
|
Self::Normal => None,
|
||||||
Self::Select(start, indices) => Some((start, indices)),
|
Self::Select(visual) | Self::Unset(visual) => Some(visual.indices(end, len)),
|
||||||
Self::Unset(start, indices) => Some((start, indices)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mode {
|
|
||||||
pub fn is_select(&self) -> bool { matches!(self, Self::Select(..)) }
|
|
||||||
|
|
||||||
pub fn is_unset(&self) -> bool { matches!(self, Self::Unset(..)) }
|
|
||||||
|
|
||||||
pub fn is_visual(&self) -> bool { matches!(self, Self::Select(..) | Self::Unset(..)) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Mode {
|
impl Display for Mode {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.write_str(match self {
|
f.write_str(match self {
|
||||||
Self::Normal => "normal",
|
Self::Normal => "normal",
|
||||||
Self::Select(..) => "select",
|
Self::Select(..) => "select",
|
||||||
|
|
|
||||||
|
|
@ -23,14 +23,16 @@ impl TryFrom<Table> for PreviewLock {
|
||||||
|
|
||||||
fn try_from(t: Table) -> Result<Self, Self::Error> {
|
fn try_from(t: Table) -> Result<Self, Self::Error> {
|
||||||
let file: FileRef = t.raw_get("file")?;
|
let file: FileRef = t.raw_get("file")?;
|
||||||
Ok(Self {
|
file.borrow(|f| {
|
||||||
url: file.url_owned(),
|
Ok(Self {
|
||||||
cha: file.cha,
|
url: f.url_owned(),
|
||||||
mime: t.raw_get::<mlua::String>("mime")?.to_str()?.intern(),
|
cha: f.cha,
|
||||||
|
mime: t.raw_get::<mlua::String>("mime")?.to_str()?.intern(),
|
||||||
|
|
||||||
skip: t.raw_get("skip")?,
|
skip: t.raw_get("skip")?,
|
||||||
area: t.raw_get("area")?,
|
area: t.raw_get("area")?,
|
||||||
data: Default::default(),
|
data: Default::default(),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
49
yazi-core/src/tab/visual.rs
Normal file
49
yazi-core/src/tab/visual.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
use std::{iter::Chain, ops::Range};
|
||||||
|
|
||||||
|
use yazi_widgets::Step;
|
||||||
|
|
||||||
|
pub type VisualIndices = Chain<Range<usize>, Range<usize>>;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub struct Visual {
|
||||||
|
start: usize,
|
||||||
|
wraps: isize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Visual {
|
||||||
|
pub fn new(start: usize) -> Self { Self { start, wraps: 0 } }
|
||||||
|
|
||||||
|
pub fn arrow(&mut self, step: Step, old: usize, new: usize) {
|
||||||
|
self.wraps += match step {
|
||||||
|
Step::Prev if new > old => -1,
|
||||||
|
Step::Next if new < old => 1,
|
||||||
|
_ => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn contains(&self, index: usize, end: usize, len: usize) -> bool {
|
||||||
|
let (first, second) = self.ranges(end, len);
|
||||||
|
first.contains(&index) || second.contains(&index)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn indices(&self, end: usize, len: usize) -> VisualIndices {
|
||||||
|
let (first, second) = self.ranges(end, len);
|
||||||
|
first.chain(second)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ranges(&self, end: usize, len: usize) -> (Range<usize>, Range<usize>) {
|
||||||
|
if len == 0 {
|
||||||
|
return (0..0, 0..0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let start = self.start.min(len - 1);
|
||||||
|
let end = end.min(len - 1);
|
||||||
|
|
||||||
|
match self.wraps {
|
||||||
|
0 => (start.min(end)..start.max(end) + 1, 0..0),
|
||||||
|
1 if start > end + 1 => (0..end + 1, start..len),
|
||||||
|
-1 if end > start + 1 => (0..start + 1, end..len),
|
||||||
|
_ => (0..len, 0..0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -18,11 +18,12 @@ impl<'a> Input<'a> {
|
||||||
let input = &self.core.input.main;
|
let input = &self.core.input.main;
|
||||||
|
|
||||||
let is_dir = input.value().ends_with(CROSS_SEPARATOR);
|
let is_dir = input.value().ends_with(CROSS_SEPARATOR);
|
||||||
let is_hovered = input.id.starts_with("rename-");
|
let is_hovered = input.name.starts_with("rename-");
|
||||||
|
|
||||||
let (path, mode): (&str, u16) = match input.id.as_str() {
|
let (path, mode): (&str, u16) = match input.name.as_str() {
|
||||||
"cd" => (input.value(), if is_dir { 0o40755 } else { 0o100644 }),
|
"cd" => (input.value(), if is_dir { 0o40755 } else { 0o100644 }),
|
||||||
"create" => (input.value(), if is_dir { 0o40755 } else { 0o100644 }),
|
"create-file" => (input.value(), if is_dir { 0o40755 } else { 0o100644 }),
|
||||||
|
"create-dir" => (input.value(), 0o40755),
|
||||||
"rename-file" => (input.value(), 0o100644),
|
"rename-file" => (input.value(), 0o100644),
|
||||||
"rename-dir" => (input.value(), 0o40755),
|
"rename-dir" => (input.value(), 0o40755),
|
||||||
"shell" => ("icon.sh", 0o100644),
|
"shell" => ("icon.sh", 0o100644),
|
||||||
|
|
|
||||||
49
yazi-fs/src/file/file_ref.rs
Normal file
49
yazi-fs/src/file/file_ref.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use mlua::{AnyUserData, ExternalError, FromLua, Lua, Value};
|
||||||
|
|
||||||
|
use crate::file::{File, FileInventory};
|
||||||
|
|
||||||
|
pub struct FileRef(pub(super) AnyUserData);
|
||||||
|
|
||||||
|
const EXPECTED: &str = "expected a table or File";
|
||||||
|
|
||||||
|
impl TryFrom<FileRef> for File {
|
||||||
|
type Error = mlua::Error;
|
||||||
|
|
||||||
|
fn try_from(value: FileRef) -> Result<Self, Self::Error> { value.borrow(|f| Ok(f.clone())) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TryFrom<FileRef> for Cow<'a, File> {
|
||||||
|
type Error = mlua::Error;
|
||||||
|
|
||||||
|
fn try_from(value: FileRef) -> Result<Self, Self::Error> { File::try_from(value).map(Cow::Owned) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileRef {
|
||||||
|
pub fn borrow<R>(&self, mut f: impl FnMut(&File) -> mlua::Result<R>) -> mlua::Result<R> {
|
||||||
|
let mut result = None;
|
||||||
|
for inv in inventory::iter::<FileInventory>() {
|
||||||
|
match (inv.borrow)(&self.0, &mut |file| Ok(result = Some(f(file)?))) {
|
||||||
|
Ok(()) => return Ok(result.unwrap()),
|
||||||
|
Err(mlua::Error::UserDataTypeMismatch) => continue,
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.0.borrow::<File>() {
|
||||||
|
Ok(file) => f(&file),
|
||||||
|
Err(mlua::Error::UserDataTypeMismatch) => Err(mlua::Error::UserDataTypeMismatch),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromLua for FileRef {
|
||||||
|
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
|
||||||
|
match value {
|
||||||
|
Value::UserData(ud) => Ok(Self(ud)),
|
||||||
|
_ => Err(EXPECTED.into_lua_err())?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,7 @@ use crate::file::File;
|
||||||
|
|
||||||
pub struct FileInventory {
|
pub struct FileInventory {
|
||||||
pub register: fn(&mut UserDataRegistry<File>),
|
pub register: fn(&mut UserDataRegistry<File>),
|
||||||
pub from_lua: fn(&AnyUserData) -> mlua::Result<File>,
|
pub borrow: fn(&AnyUserData, &mut dyn FnMut(&File) -> mlua::Result<()>) -> mlua::Result<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
inventory::collect!(FileInventory);
|
inventory::collect!(FileInventory);
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
use mlua::{AnyUserData, ExternalError, FromLua, Lua, Table, UserData, UserDataMethods, UserDataRef, UserDataRegistry, Value};
|
use mlua::{AnyUserData, ExternalError, FromLua, Lua, Table, UserData, UserDataMethods, UserDataRegistry, Value};
|
||||||
use yazi_binding::{impl_file_fields, impl_file_methods};
|
use yazi_binding::{impl_file_fields, impl_file_methods};
|
||||||
|
|
||||||
use crate::file::{File, FileInventory};
|
use crate::file::{File, FileInventory, FileRef};
|
||||||
|
|
||||||
pub type FileRef = UserDataRef<File>;
|
|
||||||
|
|
||||||
const EXPECTED: &str = "expected a table, File, or fs::File";
|
const EXPECTED: &str = "expected a table, File, or fs::File";
|
||||||
|
|
||||||
|
|
@ -25,19 +23,11 @@ impl TryFrom<AnyUserData> for File {
|
||||||
type Error = mlua::Error;
|
type Error = mlua::Error;
|
||||||
|
|
||||||
fn try_from(value: AnyUserData) -> Result<Self, Self::Error> {
|
fn try_from(value: AnyUserData) -> Result<Self, Self::Error> {
|
||||||
if let Ok(me) = value.take::<Self>() {
|
match value.take::<Self>() {
|
||||||
return Ok(me);
|
Ok(me) => Ok(me),
|
||||||
|
Err(mlua::Error::UserDataTypeMismatch) => FileRef(value).try_into(),
|
||||||
|
Err(e) => Err(e),
|
||||||
}
|
}
|
||||||
|
|
||||||
for inv in inventory::iter::<FileInventory>() {
|
|
||||||
match (inv.from_lua)(&value) {
|
|
||||||
Ok(file) => return Ok(file),
|
|
||||||
Err(mlua::Error::UserDataTypeMismatch) => continue,
|
|
||||||
Err(e) => return Err(e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(mlua::Error::UserDataTypeMismatch)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
yazi_macro::mod_flat!(data file_cov file files inventory lua);
|
yazi_macro::mod_flat!(data file_cov file file_ref files inventory lua);
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ function Entity:padding()
|
||||||
end
|
end
|
||||||
|
|
||||||
function Entity:icon()
|
function Entity:icon()
|
||||||
local icon = self._file:icon()
|
local icon = th.icon:match(self._file, { hovered = self._file.is_hovered })
|
||||||
if not icon then
|
if not icon then
|
||||||
return ""
|
return ""
|
||||||
elseif self._file.is_hovered then
|
elseif self._file.is_hovered then
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,10 @@ function M:peek(job)
|
||||||
local left, right = {}, {}
|
local left, right = {}, {}
|
||||||
for i = job.skip + 1, #items do
|
for i = job.skip + 1, #items do
|
||||||
local f = items[i]
|
local f = items[i]
|
||||||
local icon = File({
|
local icon = th.icon:match(File {
|
||||||
url = Url(f.path),
|
url = Url(f.path),
|
||||||
cha = Cha { mode = tonumber(f.is_dir and "40700" or "100644", 8) },
|
cha = Cha { mode = tonumber(f.is_dir and "40700" or "100644", 8) },
|
||||||
}):icon()
|
})
|
||||||
|
|
||||||
if f.size > 0 then
|
if f.size > 0 then
|
||||||
right[#right + 1] = " " .. ya.readable_size(f.size) .. " "
|
right[#right + 1] = " " .. ya.readable_size(f.size) .. " "
|
||||||
|
|
|
||||||
23
yazi-plugin/src/theme/icon.rs
Normal file
23
yazi-plugin/src/theme/icon.rs
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
use mlua::{IntoLua, Lua, Table, Value};
|
||||||
|
use yazi_binding::{Composer, ComposerGet, ComposerSet};
|
||||||
|
use yazi_config::THEME;
|
||||||
|
use yazi_fs::file::FileRef;
|
||||||
|
|
||||||
|
pub(super) fn icon() -> Composer<ComposerGet, ComposerSet> {
|
||||||
|
fn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {
|
||||||
|
match key {
|
||||||
|
b"match" => lua
|
||||||
|
.create_function(|_, (_, file, opts): (Value, FileRef, Option<Table>)| {
|
||||||
|
let hovered = opts.map(|t| t.raw_get("hovered")).transpose()?.unwrap_or(false);
|
||||||
|
file.borrow(|f| Ok(THEME.icon.matches(f, hovered)))
|
||||||
|
})?
|
||||||
|
.into_lua(lua),
|
||||||
|
|
||||||
|
_ => Ok(Value::Nil),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result<Value> { Ok(value) }
|
||||||
|
|
||||||
|
Composer::new(get, set)
|
||||||
|
}
|
||||||
|
|
@ -1 +1 @@
|
||||||
yazi_macro::mod_flat!(theme);
|
yazi_macro::mod_flat!(icon theme);
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use yazi_binding::{Composer, ComposerGet, ComposerSet, style::Style};
|
||||||
use yazi_config::{THEME, theme::CustomSectionArc};
|
use yazi_config::{THEME, theme::CustomSectionArc};
|
||||||
use yazi_shared::url::UrlBuf;
|
use yazi_shared::url::UrlBuf;
|
||||||
|
|
||||||
use crate::LUA;
|
use crate::{LUA, theme::icon};
|
||||||
|
|
||||||
pub fn compose() -> Composer<ComposerGet, ComposerSet> {
|
pub fn compose() -> Composer<ComposerGet, ComposerSet> {
|
||||||
fn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {
|
fn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {
|
||||||
|
|
@ -23,6 +23,7 @@ pub fn compose() -> Composer<ComposerGet, ComposerSet> {
|
||||||
b"cmp" => cmp(),
|
b"cmp" => cmp(),
|
||||||
b"tasks" => tasks(),
|
b"tasks" => tasks(),
|
||||||
b"help" => help(),
|
b"help" => help(),
|
||||||
|
b"icon" => icon(),
|
||||||
_ => return custom(lua, key),
|
_ => return custom(lua, key),
|
||||||
}
|
}
|
||||||
.into_lua(lua)
|
.into_lua(lua)
|
||||||
|
|
|
||||||
|
|
@ -12,18 +12,20 @@ impl Utils {
|
||||||
pub(super) fn file_cache(lua: &Lua) -> mlua::Result<Function> {
|
pub(super) fn file_cache(lua: &Lua) -> mlua::Result<Function> {
|
||||||
lua.create_function(|_, t: Table| {
|
lua.create_function(|_, t: Table| {
|
||||||
let file: FileRef = t.raw_get("file")?;
|
let file: FileRef = t.raw_get("file")?;
|
||||||
if file.url.parent() == Some(yazi_shared::url::Url::regular(&YAZI.preview.cache_dir)) {
|
file.borrow(|f| {
|
||||||
return Ok(None);
|
if f.url.parent() == Some(yazi_shared::url::Url::regular(&YAZI.preview.cache_dir)) {
|
||||||
}
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
let hex = {
|
let hex = {
|
||||||
let mut h = Twox128::default();
|
let mut h = Twox128::default();
|
||||||
file.hash(&mut h);
|
f.hash(&mut h);
|
||||||
t.raw_get("skip").unwrap_or(0usize).hash(&mut h);
|
t.raw_get("skip").unwrap_or(0usize).hash(&mut h);
|
||||||
format!("{:x}", h.finish_128())
|
format!("{:x}", h.finish_128())
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Some(UrlBuf::from(YAZI.preview.cache_dir.join(hex))))
|
Ok(Some(UrlBuf::from(YAZI.preview.cache_dir.join(hex))))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use std::{io::{self, Write}, os::windows::io::AsRawHandle};
|
use std::{io::{self, Write}, os::windows::io::AsRawHandle};
|
||||||
|
|
||||||
use windows_sys::Win32::System::Console::{CONSOLE_MODE, GetConsoleCP, GetConsoleMode, GetConsoleOutputCP, SetConsoleCP, SetConsoleMode, SetConsoleOutputCP};
|
use windows_sys::Win32::System::Console::{CONSOLE_MODE, FlushConsoleInputBuffer, GetConsoleCP, GetConsoleMode, GetConsoleOutputCP, SetConsoleCP, SetConsoleMode, SetConsoleOutputCP};
|
||||||
use yazi_shim::{bool_ok, nz_ok};
|
use yazi_shim::{bool_ok, nz_ok};
|
||||||
use yazi_tty::Tty;
|
use yazi_tty::Tty;
|
||||||
|
|
||||||
|
|
@ -29,6 +29,7 @@ impl Restorer {
|
||||||
pub fn restore(&self, tty: &Tty) {
|
pub fn restore(&self, tty: &Tty) {
|
||||||
tty.writer().flush().ok();
|
tty.writer().flush().ok();
|
||||||
unsafe {
|
unsafe {
|
||||||
|
FlushConsoleInputBuffer(tty.reader().as_raw_handle());
|
||||||
SetConsoleCP(self.input_cp);
|
SetConsoleCP(self.input_cp);
|
||||||
SetConsoleMode(tty.reader().as_raw_handle(), self.input_mode);
|
SetConsoleMode(tty.reader().as_raw_handle(), self.input_mode);
|
||||||
SetConsoleOutputCP(self.output_cp);
|
SetConsoleOutputCP(self.output_cp);
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use crate::input::{InputCallback, InputStyles};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct InputOpt {
|
pub struct InputOpt {
|
||||||
pub id: String,
|
pub name: String,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub value: String,
|
pub value: String,
|
||||||
pub styles: InputStyles,
|
pub styles: InputStyles,
|
||||||
|
|
@ -43,7 +43,7 @@ impl TryFrom<&Table> for InputOpt {
|
||||||
|
|
||||||
fn try_from(t: &Table) -> Result<Self, Self::Error> {
|
fn try_from(t: &Table) -> Result<Self, Self::Error> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
id: t.raw_get("id").unwrap_or_default(),
|
name: t.raw_get("name").unwrap_or_default(),
|
||||||
title: t.raw_get("title").unwrap_or_default(),
|
title: t.raw_get("title").unwrap_or_default(),
|
||||||
value: t.raw_get("value").unwrap_or_default(),
|
value: t.raw_get("value").unwrap_or_default(),
|
||||||
styles: t.raw_get("styles")?,
|
styles: t.raw_get("styles")?,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue