From b6f1aa552f51c2cb86d8e535095b64e43adbbcd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E9=9B=85=20=C2=B7=20Misaki=20Masa?= Date: Wed, 9 Aug 2023 18:15:02 +0800 Subject: [PATCH] feat: automatically change the current working directory after closing yazi (#40) --- Cargo.lock | 166 ++++++++++++++++++++++++++++++++++ README.md | 13 +++ adaptor/src/image.rs | 6 +- app/src/app.rs | 15 ++- app/src/executor.rs | 4 +- app/src/logs.rs | 4 +- config/Cargo.toml | 1 + config/src/boot/boot.rs | 39 ++++++++ config/src/boot/mod.rs | 3 + config/src/lib.rs | 3 + config/src/log/log.rs | 14 +-- config/src/manager/manager.rs | 17 +--- core/src/manager/manager.rs | 17 ++-- core/src/manager/tabs.rs | 4 +- cspell.json | 2 +- 15 files changed, 261 insertions(+), 47 deletions(-) create mode 100644 config/src/boot/boot.rs create mode 100644 config/src/boot/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 3d25ad41..e99e9f20 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -60,6 +60,55 @@ dependencies = [ "thiserror", ] +[[package]] +name = "anstream" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" + +[[package]] +name = "anstyle-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "anstyle-wincon" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +dependencies = [ + "anstyle", + "windows-sys 0.48.0", +] + [[package]] name = "anyhow" version = "1.0.72" @@ -218,12 +267,59 @@ dependencies = [ "winapi", ] +[[package]] +name = "clap" +version = "4.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c27cdf28c0f604ba3f512b0c9a409f8de8513e4816705deb0498b627e7c3a3fd" +dependencies = [ + "clap_builder", + "clap_derive", + "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08a9f1ab5e9f01a9b81f202e8562eb9a10de70abf9eaeac1be465c28b75aa4aa" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" + [[package]] name = "color_quant" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "concurrent-queue" version = "2.2.0" @@ -238,6 +334,7 @@ name = "config" version = "0.1.0" dependencies = [ "anyhow", + "clap", "crossterm 0.27.0", "futures", "glob", @@ -419,6 +516,27 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "errno" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "event-listener" version = "2.5.3" @@ -664,6 +782,12 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "hermit-abi" version = "0.3.2" @@ -768,6 +892,17 @@ dependencies = [ "libc", ] +[[package]] +name = "is-terminal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +dependencies = [ + "hermit-abi", + "rustix", + "windows-sys 0.48.0", +] + [[package]] name = "itoa" version = "1.0.9" @@ -845,6 +980,12 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +[[package]] +name = "linux-raw-sys" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" + [[package]] name = "lock_api" version = "0.4.10" @@ -1254,6 +1395,19 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustix" +version = "0.38.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "172891ebdceb05aa0005f533a6cbfca599ddd7d966f6f5d4d9b2e70478e70399" +dependencies = [ + "bitflags 2.3.3", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.48.0", +] + [[package]] name = "ryu" version = "1.0.15" @@ -1424,6 +1578,12 @@ dependencies = [ "lock_api", ] +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "syn" version = "2.0.28" @@ -1741,6 +1901,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "valuable" version = "0.1.0" diff --git a/README.md b/README.md index b4d2cb15..291b4a16 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,19 @@ yazi If you want to use your own config, copy the [config folder](https://github.com/sxyazi/yazi/tree/main/config/preset) to `~/.config/yazi`, and modify it as you like. +There is a wrapper of yazi that provides the ability to change the current shell's working directory when yazi exited, feel free to use it: + +```bash +function ya() { + tmp="$(mktemp -t "yazi-cwd")" + yazi --cwd-file="$tmp" + if cwd="$(cat -- "$tmp")" && [ -n "$cwd" ] && [ "$cwd" != "$PWD" ]; then + cd -- "$cwd" + fi + rm -f -- "$tmp" +} +``` + ## Image Preview | Platform | Protocol | Support | diff --git a/adaptor/src/image.rs b/adaptor/src/image.rs index 1647a342..b1d6cbc3 100644 --- a/adaptor/src/image.rs +++ b/adaptor/src/image.rs @@ -1,7 +1,7 @@ use std::{path::{Path, PathBuf}, sync::Arc}; use anyhow::Result; -use config::{MANAGER, PREVIEW}; +use config::{BOOT, PREVIEW}; use image::{imageops::FilterType, DynamicImage, ImageFormat}; use md5::{Digest, Md5}; use shared::tty_ratio; @@ -57,8 +57,8 @@ impl Image { #[inline] pub fn cache(path: &Path) -> PathBuf { - MANAGER - .cache + BOOT + .cache_dir .join(format!("{:x}", Md5::new_with_prefix(path.to_string_lossy().as_bytes()).finalize())) } } diff --git a/app/src/app.rs b/app/src/app.rs index 2bb3e21f..b7722650 100644 --- a/app/src/app.rs +++ b/app/src/app.rs @@ -1,7 +1,8 @@ use core::{emit, files::FilesOp, input::InputMode, Event}; +use std::os::unix::prelude::OsStrExt; use anyhow::{Ok, Result}; -use config::keymap::{Control, Key, KeymapLayer}; +use config::{keymap::{Control, Key, KeymapLayer}, BOOT}; use crossterm::event::KeyEvent; use shared::{absolute_path, Term}; use tokio::sync::oneshot; @@ -24,7 +25,10 @@ impl App { while let Some(event) = app.signals.recv().await { match event { - Event::Quit => break, + Event::Quit => { + app.dispatch_quit(); + break; + } Event::Key(key) => app.dispatch_key(key), Event::Paste(str) => app.dispatch_paste(str), Event::Render(_) => app.dispatch_render(), @@ -37,6 +41,13 @@ impl App { Ok(()) } + fn dispatch_quit(&mut self) { + if let Some(p) = &BOOT.cwd_file { + let cwd = self.cx.manager.cwd().as_os_str(); + std::fs::write(p, cwd.as_bytes()).ok(); + } + } + fn dispatch_key(&mut self, key: KeyEvent) { let key = Key::from(key); if Executor::handle(&mut self.cx, key) { diff --git a/app/src/executor.rs b/app/src/executor.rs index 7eea35b3..437bcef4 100644 --- a/app/src/executor.rs +++ b/app/src/executor.rs @@ -86,7 +86,7 @@ impl Executor { "open" => cx.manager.open(exec.named.contains_key("interactive")), "yank" => cx.manager.yank(exec.named.contains_key("cut")), "paste" => { - let dest = cx.manager.current().cwd.clone(); + let dest = cx.manager.cwd().to_owned(); let (cut, src) = cx.manager.yanked(); let force = exec.named.contains_key("force"); @@ -133,7 +133,7 @@ impl Executor { // Tabs "tab_create" => { let path = if exec.named.contains_key("current") { - cx.manager.current().cwd.clone() + cx.manager.cwd().to_owned() } else { exec.args.get(0).map(|p| p.into()).unwrap_or("/".into()) }; diff --git a/app/src/logs.rs b/app/src/logs.rs index 49604dd0..0c52703b 100644 --- a/app/src/logs.rs +++ b/app/src/logs.rs @@ -1,5 +1,5 @@ use anyhow::{Context, Result}; -use config::LOG; +use config::BOOT; use tracing_appender::non_blocking::WorkerGuard; use tracing_subscriber::{fmt, prelude::__tracing_subscriber_SubscriberExt, Registry}; @@ -7,7 +7,7 @@ pub(super) struct Logs; impl Logs { pub(super) fn init() -> Result { - let appender = tracing_appender::rolling::never(&LOG.root, "yazi.log"); + let appender = tracing_appender::rolling::never(&BOOT.state_dir, "yazi.log"); let (handle, guard) = tracing_appender::non_blocking(appender); // let filter = EnvFilter::from_default_env(); diff --git a/config/Cargo.toml b/config/Cargo.toml index f259b1ef..a2c2e90a 100644 --- a/config/Cargo.toml +++ b/config/Cargo.toml @@ -8,6 +8,7 @@ shared = { path = "../shared" } # External dependencies anyhow = "^1" +clap = { version = "^4", features = [ "derive" ] } crossterm = "^0" futures = "^0" glob = "^0" diff --git a/config/src/boot/boot.rs b/config/src/boot/boot.rs new file mode 100644 index 00000000..dc41c5d0 --- /dev/null +++ b/config/src/boot/boot.rs @@ -0,0 +1,39 @@ +use std::{env, fs, path::PathBuf}; + +use clap::Parser; + +#[derive(Debug)] +pub struct Boot { + pub cwd: PathBuf, + pub cwd_file: Option, + pub cache_dir: PathBuf, + pub state_dir: PathBuf, +} + +#[derive(Debug, Parser)] +struct Args { + /// Write the cwd on exit to this file + #[arg(long)] + cwd_file: Option, +} + +impl Default for Boot { + fn default() -> Self { + let args = Args::parse(); + let boot = Self { + cwd: env::current_dir().unwrap_or("/".into()), + cwd_file: args.cwd_file, + cache_dir: env::temp_dir().join("yazi"), + state_dir: xdg::BaseDirectories::with_prefix("yazi").unwrap().get_state_home(), + }; + + if !boot.cache_dir.is_dir() { + fs::create_dir(&boot.cache_dir).unwrap(); + } + if !boot.state_dir.is_dir() { + fs::create_dir_all(&boot.state_dir).unwrap(); + } + + boot + } +} diff --git a/config/src/boot/mod.rs b/config/src/boot/mod.rs new file mode 100644 index 00000000..5922b49a --- /dev/null +++ b/config/src/boot/mod.rs @@ -0,0 +1,3 @@ +mod boot; + +pub use boot::*; diff --git a/config/src/lib.rs b/config/src/lib.rs index b2c0b3c0..af5a5fa7 100644 --- a/config/src/lib.rs +++ b/config/src/lib.rs @@ -2,6 +2,7 @@ use once_cell::sync::Lazy; +mod boot; pub mod keymap; mod log; pub mod manager; @@ -18,6 +19,7 @@ static MERGED_KEYMAP: Lazy = Lazy::new(Preset::keymap); static MERGED_THEME: Lazy = Lazy::new(Preset::theme); static MERGED_YAZI: Lazy = Lazy::new(Preset::yazi); +pub static BOOT: Lazy = Lazy::new(Default::default); pub static KEYMAP: Lazy = Lazy::new(Default::default); pub static LOG: Lazy = Lazy::new(Default::default); pub static MANAGER: Lazy = Lazy::new(Default::default); @@ -26,6 +28,7 @@ pub static PREVIEW: Lazy = Lazy::new(Default::default); pub static THEME: Lazy = Lazy::new(Default::default); pub fn init() { + Lazy::force(&BOOT); Lazy::force(&KEYMAP); Lazy::force(&LOG); Lazy::force(&MANAGER); diff --git a/config/src/log/log.rs b/config/src/log/log.rs index e90b92cb..74433fdf 100644 --- a/config/src/log/log.rs +++ b/config/src/log/log.rs @@ -1,13 +1,10 @@ -use std::path::PathBuf; - -use serde::{de, Deserialize, Deserializer}; +use serde::{Deserialize, Deserializer}; use crate::MERGED_YAZI; #[derive(Debug)] pub struct Log { pub enabled: bool, - pub root: PathBuf, } impl Default for Log { @@ -30,13 +27,6 @@ impl<'de> Deserialize<'de> for Log { let outer = Outer::deserialize(deserializer)?; - let root = xdg::BaseDirectories::with_prefix("yazi") - .map_err(|e| de::Error::custom(e.to_string()))? - .get_state_home(); - if !root.is_dir() { - std::fs::create_dir_all(&root).map_err(|e| de::Error::custom(e.to_string()))?; - } - - Ok(Self { enabled: outer.log.enabled, root }) + Ok(Self { enabled: outer.log.enabled }) } } diff --git a/config/src/manager/manager.rs b/config/src/manager/manager.rs index c270ab79..319ef7bd 100644 --- a/config/src/manager/manager.rs +++ b/config/src/manager/manager.rs @@ -1,5 +1,3 @@ -use std::{env, fs, path::PathBuf}; - use serde::Deserialize; use super::SortBy; @@ -7,11 +5,6 @@ use crate::MERGED_YAZI; #[derive(Debug, Deserialize)] pub struct Manager { - #[serde(skip)] - pub cwd: PathBuf, - #[serde(skip)] - pub cache: PathBuf, - // Sorting pub sort_by: SortBy, pub sort_reverse: bool, @@ -27,14 +20,6 @@ impl Default for Manager { manager: Manager, } - let mut manager = toml::from_str::(&MERGED_YAZI).unwrap().manager; - - manager.cwd = env::current_dir().unwrap_or("/".into()); - manager.cache = env::temp_dir().join("yazi"); - if !manager.cache.is_dir() { - fs::create_dir(&manager.cache).unwrap(); - } - - manager + toml::from_str::(&MERGED_YAZI).unwrap().manager } } diff --git a/core/src/manager/manager.rs b/core/src/manager/manager.rs index f2f0790a..be38d48a 100644 --- a/core/src/manager/manager.rs +++ b/core/src/manager/manager.rs @@ -1,4 +1,4 @@ -use std::{collections::{BTreeMap, BTreeSet, HashMap, HashSet}, env, mem, path::PathBuf}; +use std::{collections::{BTreeMap, BTreeSet, HashMap, HashSet}, env, mem, path::{Path, PathBuf}}; use anyhow::Error; use config::{open::Opener, OPEN}; @@ -28,9 +28,9 @@ impl Manager { } pub fn refresh(&mut self) { - env::set_current_dir(&self.current().cwd).ok(); + env::set_current_dir(self.cwd()).ok(); - self.watcher.trigger(&self.current().cwd); + self.watcher.trigger(self.cwd()); if let Some(p) = self.parent() { self.watcher.trigger(&p.cwd); } @@ -174,7 +174,7 @@ impl Manager { } pub fn create(&self) -> bool { - let cwd = self.current().cwd.clone(); + let cwd = self.cwd().to_owned(); tokio::spawn(async move { let result = emit!(Input(InputOpt::top("Create:"))); @@ -248,7 +248,7 @@ impl Manager { pub fn update_read(&mut self, op: FilesOp) -> bool { let path = op.path(); - let cwd = self.current().cwd.clone(); + let cwd = self.cwd().to_owned(); let hovered = self.hovered().map(|h| h.path()); let mut b = if cwd == path && !self.current().in_search { @@ -277,7 +277,7 @@ impl Manager { pub fn update_search(&mut self, op: FilesOp) -> bool { let path = op.path(); - if self.current().in_search && self.current().cwd == path { + if self.current().in_search && self.cwd() == path { return self.current_mut().update(op); } @@ -293,7 +293,7 @@ impl Manager { let path = op.path(); let op = FilesOp::read_empty(&path); - if path == self.current().cwd { + if path == self.cwd() { self.current_mut().update(op); } else if matches!(self.parent(), Some(p) if p.cwd == path) { self.active_mut().parent.as_mut().unwrap().update(op); @@ -338,6 +338,9 @@ impl Manager { } impl Manager { + #[inline] + pub fn cwd(&self) -> &Path { &self.current().cwd } + #[inline] pub fn tabs(&self) -> &Tabs { &self.tabs } diff --git a/core/src/manager/tabs.rs b/core/src/manager/tabs.rs index bf4d2890..3539aa99 100644 --- a/core/src/manager/tabs.rs +++ b/core/src/manager/tabs.rs @@ -1,6 +1,6 @@ use std::path::Path; -use config::MANAGER; +use config::BOOT; use super::Tab; use crate::emit; @@ -14,7 +14,7 @@ pub struct Tabs { impl Tabs { pub fn make() -> Self { - let mut tabs = Self { idx: usize::MAX, items: vec![Tab::new(&MANAGER.cwd)] }; + let mut tabs = Self { idx: usize::MAX, items: vec![Tab::new(&BOOT.cwd)] }; tabs.set_idx(0); tabs } diff --git a/cspell.json b/cspell.json index fd74b8cd..ebb081b7 100644 --- a/cspell.json +++ b/cspell.json @@ -1 +1 @@ -{"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"],"language":"en","version":"0.2","flagWords":[]} +{"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"],"version":"0.2","language":"en"}