fix: use a unique Id for each tab (#1826)

This commit is contained in:
三咲雅 · Misaki Masa 2024-10-24 08:57:08 +08:00 committed by GitHub
parent 35d781e240
commit e8c1d625ac
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 143 additions and 82 deletions

View file

@ -2,21 +2,18 @@ use std::{collections::HashSet, path::PathBuf};
use yazi_dds::Pubsub;
use yazi_macro::render;
use yazi_shared::{event::{Cmd, Data}, fs::{Url, Urn}};
use yazi_shared::{Id, event::{Cmd, Data}, fs::{Url, Urn}};
use crate::manager::Manager;
struct Opt {
url: Option<Url>,
tab: Option<usize>,
tab: Option<Id>,
}
impl From<Cmd> for Opt {
fn from(mut c: Cmd) -> Self {
Self {
url: c.take_first().and_then(Data::into_url),
tab: c.get("tab").and_then(Data::as_usize),
}
Self { url: c.take_first().and_then(Data::into_url), tab: c.get("tab").and_then(Data::as_id) }
}
}
impl From<Option<Url>> for Opt {
@ -49,10 +46,10 @@ impl Manager {
self.watcher.watch(to_watch);
// Publish through DDS
Pubsub::pub_from_hover(self.active().idx, self.hovered().map(|h| &h.url));
Pubsub::pub_from_hover(self.active().id, self.hovered().map(|h| &h.url));
}
fn hover_do(&mut self, url: Url, tab: Option<usize>) {
fn hover_do(&mut self, url: Url, tab: Option<Id>) {
// Hover on the file
if let Ok(p) = url.strip_prefix(&self.current_or(tab).url).map(PathBuf::from) {
render!(self.current_or_mut(tab).repos(Some(Urn::new(&p))));

View file

@ -30,7 +30,6 @@ impl Tabs {
self.set_idx(self.absolute(1));
}
self.reorder();
render!();
}
}

View file

@ -36,8 +36,7 @@ impl Tabs {
return;
}
let mut tab = Tab { idx: self.cursor + 1, ..Default::default() };
let mut tab = Tab::default();
if !opt.current {
tab.cd(opt.url);
} else if let Some(h) = self.active().hovered() {
@ -52,7 +51,6 @@ impl Tabs {
self.items.insert(self.cursor + 1, tab);
self.set_idx(self.cursor + 1);
self.reorder();
render!();
}
}

View file

@ -21,7 +21,6 @@ impl Tabs {
self.items.swap(self.cursor, idx);
self.set_idx(idx);
self.reorder();
render!();
}
}

View file

@ -87,7 +87,7 @@ impl Manager {
return;
}
ManagerProxy::hover(None, tab.idx); // Re-hover in next loop
ManagerProxy::hover(None, tab.id); // Re-hover in next loop
ManagerProxy::update_paged(); // Update for paged files in next loop
if calc {
tasks.prework_sorted(&tab.current.files);

View file

@ -2,7 +2,7 @@ use ratatui::layout::Rect;
use yazi_adapter::Dimension;
use yazi_config::popup::{Origin, Position};
use yazi_fs::Folder;
use yazi_shared::fs::{File, Url};
use yazi_shared::{Id, fs::{File, Url}};
use super::{Mimetype, Tabs, Watcher, Yanked};
use crate::tab::Tab;
@ -48,10 +48,10 @@ impl Manager {
pub fn active_mut(&mut self) -> &mut Tab { self.tabs.active_mut() }
#[inline]
pub fn active_or(&self, idx: Option<usize>) -> &Tab { self.tabs.active_or(idx) }
pub fn active_or(&self, id: Option<Id>) -> &Tab { self.tabs.active_or(id) }
#[inline]
pub fn active_or_mut(&mut self, idx: Option<usize>) -> &mut Tab { self.tabs.active_or_mut(idx) }
pub fn active_or_mut(&mut self, id: Option<Id>) -> &mut Tab { self.tabs.active_or_mut(id) }
#[inline]
pub fn current(&self) -> &Folder { &self.active().current }
@ -60,10 +60,10 @@ impl Manager {
pub fn current_mut(&mut self) -> &mut Folder { &mut self.active_mut().current }
#[inline]
pub fn current_or(&self, idx: Option<usize>) -> &Folder { &self.active_or(idx).current }
pub fn current_or(&self, idx: Option<Id>) -> &Folder { &self.active_or(idx).current }
#[inline]
pub fn current_or_mut(&mut self, idx: Option<usize>) -> &mut Folder {
pub fn current_or_mut(&mut self, idx: Option<Id>) -> &mut Folder {
&mut self.active_or_mut(idx).current
}

View file

@ -3,7 +3,7 @@ use std::ops::{Deref, DerefMut};
use yazi_boot::BOOT;
use yazi_dds::Pubsub;
use yazi_proxy::ManagerProxy;
use yazi_shared::fs::Url;
use yazi_shared::{Id, fs::Url};
use crate::tab::Tab;
@ -16,7 +16,6 @@ impl Tabs {
pub fn make() -> Self {
let mut tabs =
Self { cursor: 0, items: (0..BOOT.cwds.len()).map(|_| Tab::default()).collect() };
tabs.reorder();
for (i, tab) in tabs.iter_mut().enumerate() {
let file = &BOOT.files[i];
@ -37,11 +36,6 @@ impl Tabs {
}
}
#[inline]
pub(super) fn reorder(&mut self) {
self.items.iter_mut().enumerate().for_each(|(i, tab)| tab.idx = i);
}
pub(super) fn set_idx(&mut self, idx: usize) {
// Reset the preview of the last active tab
if let Some(active) = self.items.get_mut(self.cursor) {
@ -63,16 +57,16 @@ impl Tabs {
pub(super) fn active_mut(&mut self) -> &mut Tab { &mut self.items[self.cursor] }
#[inline]
pub fn active_or(&self, idx: Option<usize>) -> &Tab {
idx.and_then(|i| self.items.get(i)).unwrap_or(&self.items[self.cursor])
pub fn active_or(&self, id: Option<Id>) -> &Tab {
id.and_then(|id| self.iter().find(|&t| t.id == id)).unwrap_or(self.active())
}
#[inline]
pub(super) fn active_or_mut(&mut self, idx: Option<usize>) -> &mut Tab {
if let Some(i) = idx.filter(|&i| i < self.items.len()) {
pub(super) fn active_or_mut(&mut self, id: Option<Id>) -> &mut Tab {
if let Some(i) = id.and_then(|id| self.iter().position(|t| t.id == id)) {
&mut self.items[i]
} else {
&mut self.items[self.cursor]
self.active_mut()
}
}
}

View file

@ -42,7 +42,7 @@ impl Tab {
}
}
ManagerProxy::hover(None, self.idx);
ManagerProxy::hover(None, self.id);
render!();
}
}

View file

@ -66,7 +66,7 @@ impl Tab {
self.backstack.push(opt.target.clone());
}
Pubsub::pub_from_cd(self.idx, self.cwd());
Pubsub::pub_from_cd(self.id, self.cwd());
ManagerProxy::refresh();
render!();
}

View file

@ -27,7 +27,7 @@ impl Tab {
self.current.repos(hovered.as_ref().map(|u| u.as_urn()));
if self.hovered().map(|f| f.urn()) != hovered.as_ref().map(|u| u.as_urn()) {
ManagerProxy::hover(None, self.idx);
ManagerProxy::hover(None, self.id);
}
render!();

View file

@ -15,7 +15,7 @@ impl Tab {
self.apply_files_attrs();
if hovered.as_ref() != self.hovered().map(|f| &f.url) {
ManagerProxy::hover(hovered, self.idx);
ManagerProxy::hover(hovered, self.id);
} else if self.hovered().is_some_and(|f| f.is_dir()) {
ManagerProxy::peek(true);
}

View file

@ -30,6 +30,6 @@ impl Tab {
self.cd(parent.clone());
FilesOp::Creating(parent, vec![File::from_dummy(opt.target.clone(), None)]).emit();
ManagerProxy::hover(Some(opt.target), self.idx);
ManagerProxy::hover(Some(opt.target), self.id);
}
}

View file

@ -7,14 +7,13 @@ use yazi_adapter::Dimension;
use yazi_config::{LAYOUT, popup::{Origin, Position}};
use yazi_fs::{Folder, FolderStage};
use yazi_macro::render;
use yazi_shared::fs::{File, Url};
use yazi_shared::{Id, Ids, fs::{File, Url}};
use super::{Backstack, Config, Finder, History, Mode, Preview};
use crate::tab::Selected;
#[derive(Default)]
pub struct Tab {
pub idx: usize,
pub id: Id,
pub mode: Mode,
pub conf: Config,
pub current: Folder,
@ -29,6 +28,28 @@ pub struct Tab {
pub search: Option<JoinHandle<Result<()>>>,
}
impl Default for Tab {
fn default() -> Self {
static IDS: Ids = Ids::new();
Self {
id: IDS.next(),
mode: Default::default(),
conf: Default::default(),
current: Default::default(),
parent: Default::default(),
backstack: Default::default(),
history: Default::default(),
selected: Default::default(),
preview: Default::default(),
finder: Default::default(),
search: Default::default(),
}
}
}
impl Tab {
pub fn shutdown(&mut self) {
if let Some(handle) = self.search.take() {

View file

@ -2,13 +2,13 @@ use std::borrow::Cow;
use mlua::{IntoLua, Lua, Value};
use serde::{Deserialize, Serialize};
use yazi_shared::fs::Url;
use yazi_shared::{Id, fs::Url};
use super::Body;
#[derive(Debug, Serialize, Deserialize)]
pub struct BodyCd<'a> {
pub tab: usize,
pub tab: Id,
pub url: Cow<'a, Url>,
#[serde(skip)]
dummy: bool,
@ -16,14 +16,14 @@ pub struct BodyCd<'a> {
impl<'a> BodyCd<'a> {
#[inline]
pub fn borrowed(tab: usize, url: &'a Url) -> Body<'a> {
pub fn borrowed(tab: Id, url: &'a Url) -> Body<'a> {
Self { tab, url: Cow::Borrowed(url), dummy: false }.into()
}
}
impl BodyCd<'static> {
#[inline]
pub fn dummy(tab: usize) -> Body<'static> {
pub fn dummy(tab: Id) -> Body<'static> {
Self { tab, url: Default::default(), dummy: true }.into()
}
}
@ -36,11 +36,11 @@ impl IntoLua<'_> for BodyCd<'static> {
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
if let Some(Cow::Owned(url)) = Some(self.url).filter(|_| !self.dummy) {
lua.create_table_from([
("tab", self.tab.into_lua(lua)?),
("tab", self.tab.get().into_lua(lua)?),
("url", lua.create_any_userdata(url)?.into_lua(lua)?),
])?
} else {
lua.create_table_from([("tab", self.tab)])?
lua.create_table_from([("tab", self.tab.get())])?
}
.into_lua(lua)
}

View file

@ -2,26 +2,26 @@ use std::borrow::Cow;
use mlua::{IntoLua, Lua, Value};
use serde::{Deserialize, Serialize};
use yazi_shared::fs::Url;
use yazi_shared::{Id, fs::Url};
use super::Body;
#[derive(Debug, Serialize, Deserialize)]
pub struct BodyHover<'a> {
pub tab: usize,
pub tab: Id,
pub url: Option<Cow<'a, Url>>,
}
impl<'a> BodyHover<'a> {
#[inline]
pub fn borrowed(tab: usize, url: Option<&'a Url>) -> Body<'a> {
pub fn borrowed(tab: Id, url: Option<&'a Url>) -> Body<'a> {
Self { tab, url: url.map(Cow::Borrowed) }.into()
}
}
impl BodyHover<'static> {
#[inline]
pub fn dummy(tab: usize) -> Body<'static> { Self { tab, url: None }.into() }
pub fn dummy(tab: Id) -> Body<'static> { Self { tab, url: None }.into() }
}
impl<'a> From<BodyHover<'a>> for Body<'a> {
@ -32,11 +32,11 @@ impl IntoLua<'_> for BodyHover<'static> {
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
if let Some(Cow::Owned(url)) = self.url {
lua.create_table_from([
("tab", self.tab.into_lua(lua)?),
("tab", self.tab.get().into_lua(lua)?),
("url", lua.create_any_userdata(url)?.into_lua(lua)?),
])?
} else {
lua.create_table_from([("tab", self.tab)])?
lua.create_table_from([("tab", self.tab.get())])?
}
.into_lua(lua)
}

View file

@ -3,7 +3,7 @@ use std::collections::{HashMap, HashSet};
use mlua::Function;
use parking_lot::RwLock;
use yazi_boot::BOOT;
use yazi_shared::{RoCell, fs::Url};
use yazi_shared::{Id, RoCell, fs::Url};
use crate::{Client, ID, PEERS, body::{Body, BodyBulk, BodyCd, BodyDelete, BodyHi, BodyHover, BodyMove, BodyMoveItem, BodyRename, BodyTab, BodyTrash, BodyYank}};
@ -88,7 +88,7 @@ impl Pubsub {
true
}
pub fn pub_from_cd(tab: usize, url: &Url) {
pub fn pub_from_cd(tab: Id, url: &Url) {
if LOCAL.read().contains_key("cd") {
Self::pub_(BodyCd::dummy(tab));
}
@ -100,7 +100,7 @@ impl Pubsub {
}
}
pub fn pub_from_hover(tab: usize, url: Option<&Url>) {
pub fn pub_from_hover(tab: Id, url: Option<&Url>) {
if LOCAL.read().contains_key("hover") {
Self::pub_(BodyHover::dummy(tab));
}

View file

@ -23,7 +23,7 @@ impl Tab {
pub(super) fn register(lua: &Lua) -> mlua::Result<()> {
lua.register_userdata_type::<Self>(|reg| {
reg.add_field_method_get("idx", |_, me| Ok(me.idx + 1));
reg.add_field_method_get("id", |_, me| Ok(me.id.get()));
reg.add_method("name", |lua, me, ()| {
lua.create_string(me.current.url.name().as_encoded_bytes())
});

View file

@ -1,15 +1,15 @@
use std::{collections::{HashMap, HashSet}, mem, ops::Deref, sync::atomic::Ordering};
use std::{collections::{HashMap, HashSet}, mem, ops::Deref};
use tokio::{fs::{self, DirEntry}, select, sync::mpsc::{self, UnboundedReceiver}};
use yazi_config::{MANAGER, manager::SortBy};
use yazi_shared::fs::{Cha, FILES_TICKET, File, FilesOp, Url, Urn, UrnBuf, maybe_exists};
use yazi_shared::{Id, fs::{Cha, FILES_TICKET, File, FilesOp, Url, Urn, UrnBuf, maybe_exists}};
use super::{FilesSorter, Filter};
pub struct Files {
hidden: Vec<File>,
items: Vec<File>,
ticket: u64,
ticket: Id,
version: u64,
pub revision: u64,
@ -118,7 +118,7 @@ impl Files {
impl Files {
pub fn update_full(&mut self, files: Vec<File>) {
self.ticket = FILES_TICKET.fetch_add(1, Ordering::Relaxed);
self.ticket = FILES_TICKET.next();
(self.hidden, self.items) = self.split_files(files);
if !self.items.is_empty() {
@ -126,7 +126,7 @@ impl Files {
}
}
pub fn update_part(&mut self, files: Vec<File>, ticket: u64) {
pub fn update_part(&mut self, files: Vec<File>, ticket: Id) {
if !files.is_empty() {
if ticket != self.ticket {
return;
@ -162,7 +162,7 @@ impl Files {
}
pub fn update_ioerr(&mut self) {
self.ticket = FILES_TICKET.fetch_add(1, Ordering::Relaxed);
self.ticket = FILES_TICKET.next();
self.hidden.clear();
self.items.clear();
}
@ -349,7 +349,7 @@ impl Files {
// --- Ticket
#[inline]
pub fn ticket(&self) -> u64 { self.ticket }
pub fn ticket(&self) -> Id { self.ticket }
// --- Sorter
#[inline]

View file

@ -1,13 +1,13 @@
use std::{borrow::Cow, io::Cursor, mem, path::{Path, PathBuf}, sync::atomic::{AtomicUsize, Ordering}};
use std::{borrow::Cow, io::Cursor, mem, path::{Path, PathBuf}};
use anyhow::{Result, anyhow};
use ratatui::{layout::Rect, text::{Line, Span, Text}};
use syntect::{LoadingError, dumps, easy::HighlightLines, highlighting::{self, Theme, ThemeSet}, parsing::{SyntaxReference, SyntaxSet}};
use tokio::{fs::File, io::{AsyncBufReadExt, BufReader}, sync::OnceCell};
use yazi_config::{PREVIEW, THEME, preview::PreviewWrap};
use yazi_shared::{errors::PeekError, replace_to_printable};
use yazi_shared::{Ids, errors::PeekError, replace_to_printable};
static INCR: AtomicUsize = AtomicUsize::new(0);
static INCR: Ids = Ids::new();
static SYNTECT: OnceCell<(Theme, SyntaxSet)> = OnceCell::const_new();
pub struct Highlighter {
@ -39,7 +39,7 @@ impl Highlighter {
}
#[inline]
pub fn abort() { INCR.fetch_add(1, Ordering::Relaxed); }
pub fn abort() { INCR.next(); }
pub async fn highlight(&self, skip: usize, area: Rect) -> Result<Text<'static>, PeekError> {
let mut reader = BufReader::new(File::open(&self.path).await?);
@ -104,13 +104,13 @@ impl Highlighter {
after: Vec<String>,
syntax: &'static SyntaxReference,
) -> Result<Text<'static>, PeekError> {
let ticket = INCR.load(Ordering::Relaxed);
let ticket = INCR.current();
let (theme, syntaxes) = Self::init().await;
tokio::task::spawn_blocking(move || {
let mut h = HighlightLines::new(syntax, theme);
for line in before {
if ticket != INCR.load(Ordering::Relaxed) {
if ticket != INCR.current() {
return Err("Highlighting cancelled".into());
}
h.highlight_line(&line, syntaxes).map_err(|e| anyhow!(e))?;
@ -119,7 +119,7 @@ impl Highlighter {
let indent = PREVIEW.indent();
let mut lines = Vec::with_capacity(after.len());
for line in after {
if ticket != INCR.load(Ordering::Relaxed) {
if ticket != INCR.current() {
return Err("Highlighting cancelled".into());
}

View file

@ -1,5 +1,5 @@
use yazi_macro::emit;
use yazi_shared::{Layer, event::Cmd, fs::Url};
use yazi_shared::{Id, Layer, event::Cmd, fs::Url};
use crate::options::OpenDoOpt;
@ -12,7 +12,7 @@ impl ManagerProxy {
}
#[inline]
pub fn hover(url: Option<Url>, tab: usize) {
pub fn hover(url: Option<Url>, tab: Id) {
emit!(Call(
Cmd::args("hover", &url.map_or_else(Vec::new, |u| vec![u])).with("tab", tab),
Layer::Manager

View file

@ -143,3 +143,5 @@ impl_integer_as!(isize, as_isize);
impl_integer_as!(i16, as_i16);
impl_number_as!(f64, as_f64);
impl_integer_as!(crate::Id, as_id);

View file

@ -1,15 +1,15 @@
use std::{collections::{HashMap, HashSet}, sync::atomic::{AtomicU64, Ordering}};
use std::collections::{HashMap, HashSet};
use super::{Cha, File, UrnBuf};
use crate::{Layer, event::Cmd, fs::Url};
use crate::{Id, Ids, Layer, event::Cmd, fs::Url};
pub static FILES_TICKET: AtomicU64 = AtomicU64::new(0);
pub static FILES_TICKET: Ids = Ids::new();
#[derive(Clone, Debug)]
pub enum FilesOp {
Full(Url, Vec<File>, Cha),
Part(Url, Vec<File>, u64),
Done(Url, Cha, u64),
Part(Url, Vec<File>, Id),
Done(Url, Cha, Id),
Size(Url, HashMap<UrnBuf, u64>),
IOErr(Url, std::io::ErrorKind),
@ -41,8 +41,8 @@ impl FilesOp {
crate::event::Event::Call(Cmd::new("update_files").with_any("op", self), Layer::Manager).emit();
}
pub fn prepare(cwd: &Url) -> u64 {
let ticket = FILES_TICKET.fetch_add(1, Ordering::Relaxed);
pub fn prepare(cwd: &Url) -> Id {
let ticket = FILES_TICKET.next();
Self::Part(cwd.clone(), vec![], ticket).emit();
ticket
}

54
yazi-shared/src/id.rs Normal file
View file

@ -0,0 +1,54 @@
use std::{fmt::Display, str::FromStr, sync::atomic::{AtomicUsize, Ordering}};
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, Default, Hash, Eq, PartialEq, Serialize, Deserialize)]
pub struct Id(usize);
impl Id {
#[inline]
pub fn get(&self) -> usize { self.0 }
}
impl Display for Id {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.0.fmt(f) }
}
impl FromStr for Id {
type Err = <usize as FromStr>::Err;
fn from_str(s: &str) -> Result<Self, Self::Err> { s.parse().map(Self) }
}
impl TryFrom<i64> for Id {
type Error = <usize as TryFrom<i64>>::Error;
fn try_from(value: i64) -> Result<Self, Self::Error> { usize::try_from(value).map(Self) }
}
// --- Ids
pub struct Ids {
next: AtomicUsize,
}
impl Ids {
#[inline]
pub const fn new() -> Self { Self { next: AtomicUsize::new(1) } }
#[inline]
pub fn next(&self) -> Id {
loop {
let old = self.next.fetch_add(1, Ordering::Relaxed);
if old != 0 {
return Id(old);
}
}
}
#[inline]
pub fn current(&self) -> Id { Id(self.next.load(Ordering::Relaxed)) }
}
impl Default for Ids {
fn default() -> Self { Self::new() }
}

View file

@ -2,10 +2,7 @@
yazi_macro::mod_pub!(errors event fs shell theme translit);
yazi_macro::mod_flat!(
chars condition debounce env layer natsort number os rand ro_cell terminal throttle
time xdg
);
yazi_macro::mod_flat!(chars condition debounce env id layer natsort number os rand ro_cell terminal throttle time xdg);
pub fn init() {
#[cfg(unix)]