mirror of
https://github.com/Llewellynvdm/zoxide.git
synced 2024-11-10 23:30:57 +00:00
Copy owner from previous database file (#364)
This commit is contained in:
parent
90e781a192
commit
17365710af
19
CHANGELOG.md
19
CHANGELOG.md
@ -15,11 +15,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Bash/Zsh: rename `_z` completion function to avoid conflicts with other shell plugins.
|
- Bash/Zsh: rename `_z` completion function to avoid conflicts with other shell
|
||||||
|
plugins.
|
||||||
- Elvish: upgrade to new lambda syntax.
|
- Elvish: upgrade to new lambda syntax.
|
||||||
- Fzf: added `--keep-right` option by default, upgraded minimum version to v0.21.0.
|
- Fzf: added `--keep-right` option by default, upgraded minimum version to
|
||||||
|
v0.21.0.
|
||||||
- Bash: only enable completions on 4.4+.
|
- Bash: only enable completions on 4.4+.
|
||||||
- Fzf: bypass `ls` alias in preview window.
|
- Fzf: bypass `ls` alias in preview window.
|
||||||
|
- Retain ownership of database file.
|
||||||
|
|
||||||
## [0.8.0] - 2021-12-25
|
## [0.8.0] - 2021-12-25
|
||||||
|
|
||||||
@ -30,7 +33,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Fzf: better default options.
|
- Fzf: better default options.
|
||||||
- Fish: interactive completions are only triggered when the last argument is empty.
|
- Fish: interactive completions are only triggered when the last argument is
|
||||||
|
empty.
|
||||||
- PowerShell: installation instructions.
|
- PowerShell: installation instructions.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
@ -131,7 +135,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
- Auto-generated shell completions.
|
- Auto-generated shell completions.
|
||||||
- `zoxide query --all` for listing deleted directories.
|
- `zoxide query --all` for listing deleted directories.
|
||||||
- Lazy deletion for removed directories that have not been accessed in > 90 days.
|
- Lazy deletion for removed directories that have not been accessed in > 90
|
||||||
|
days.
|
||||||
- Nushell: support for 0.32.0+.
|
- Nushell: support for 0.32.0+.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
@ -158,7 +163,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- `cd -` on fish shells.
|
- `cd -` on fish shells.
|
||||||
- `__zoxide_hook` no longer changes value of `$?` within `$PROMPT_COMMAND` on bash.
|
- `__zoxide_hook` no longer changes value of `$?` within `$PROMPT_COMMAND` on
|
||||||
|
bash.
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
@ -195,7 +201,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
### Added
|
### Added
|
||||||
|
|
||||||
- `$_ZO_EXCLUDE_DIRS` now supports globs.
|
- `$_ZO_EXCLUDE_DIRS` now supports globs.
|
||||||
- `zoxide init` now defines `__zoxide_z*` functions that can be aliased as needed.
|
- `zoxide init` now defines `__zoxide_z*` functions that can be aliased as
|
||||||
|
needed.
|
||||||
- Support for the [xonsh](https://xon.sh/) shell.
|
- Support for the [xonsh](https://xon.sh/) shell.
|
||||||
- `zoxide import` can now import from Autojump.
|
- `zoxide import` can now import from Autojump.
|
||||||
|
|
||||||
|
82
Cargo.lock
generated
82
Cargo.lock
generated
@ -13,9 +13,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.55"
|
version = "1.0.56"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "159bb86af3a200e19a068f4224eae4c8bb2d0fa054c7e5d1cacd5cef95e684cd"
|
checksum = "4361135be9122e0870de935d7c439aef945b9f9ddd4199a553b5270b49c82a27"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "askama"
|
name = "askama"
|
||||||
@ -117,6 +117,12 @@ dependencies = [
|
|||||||
"regex-automata",
|
"regex-automata",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cc"
|
||||||
|
version = "1.0.73"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@ -125,9 +131,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "3.1.5"
|
version = "3.1.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ced1892c55c910c1219e98d6fc8d71f6bddba7905866ce740066d8bfea859312"
|
checksum = "d8c93436c21e4698bacadf42917db28b23017027a4deccb35dbe47a7e7840123"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atty",
|
"atty",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
@ -365,6 +371,15 @@ version = "2.4.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
|
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memoffset"
|
||||||
|
version = "0.6.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mime"
|
name = "mime"
|
||||||
version = "0.3.16"
|
version = "0.3.16"
|
||||||
@ -387,6 +402,19 @@ version = "0.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nix"
|
||||||
|
version = "0.23.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"cc",
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"memoffset",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nom"
|
name = "nom"
|
||||||
version = "7.1.0"
|
version = "7.1.0"
|
||||||
@ -500,24 +528,6 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand"
|
|
||||||
version = "0.8.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
|
||||||
dependencies = [
|
|
||||||
"rand_core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand_core"
|
|
||||||
version = "0.6.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
|
|
||||||
dependencies = [
|
|
||||||
"getrandom",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.2.11"
|
version = "0.2.11"
|
||||||
@ -539,9 +549,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.5.4"
|
version = "1.5.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
|
checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
@ -695,26 +705,6 @@ version = "0.15.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
|
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "thiserror"
|
|
||||||
version = "1.0.30"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
|
|
||||||
dependencies = [
|
|
||||||
"thiserror-impl",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "thiserror-impl"
|
|
||||||
version = "1.0.30"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thread_local"
|
name = "thread_local"
|
||||||
version = "1.1.4"
|
version = "1.1.4"
|
||||||
@ -825,12 +815,12 @@ dependencies = [
|
|||||||
"clap_complete_fig",
|
"clap_complete_fig",
|
||||||
"dirs",
|
"dirs",
|
||||||
"dunce",
|
"dunce",
|
||||||
|
"fastrand",
|
||||||
"glob",
|
"glob",
|
||||||
|
"nix",
|
||||||
"ordered-float",
|
"ordered-float",
|
||||||
"rand",
|
|
||||||
"rstest",
|
"rstest",
|
||||||
"rstest_reuse",
|
"rstest_reuse",
|
||||||
"serde",
|
"serde",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"thiserror",
|
|
||||||
]
|
]
|
||||||
|
13
Cargo.toml
13
Cargo.toml
@ -23,17 +23,13 @@ bincode = "1.3.1"
|
|||||||
clap = { version = "3.1.0", features = ["derive"] }
|
clap = { version = "3.1.0", features = ["derive"] }
|
||||||
dirs = "4.0.0"
|
dirs = "4.0.0"
|
||||||
dunce = "1.0.1"
|
dunce = "1.0.1"
|
||||||
|
fastrand = "1.7.0"
|
||||||
glob = "0.3.0"
|
glob = "0.3.0"
|
||||||
ordered-float = "2.0.0"
|
ordered-float = "2.0.0"
|
||||||
serde = { version = "1.0.116", features = ["derive"] }
|
serde = { version = "1.0.116", features = ["derive"] }
|
||||||
tempfile = "3.1.0"
|
|
||||||
thiserror = "1.0.30"
|
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
rand = { version = "0.8.4", features = [
|
nix = "0.23.1"
|
||||||
"getrandom",
|
|
||||||
"small_rng",
|
|
||||||
], default-features = false }
|
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
clap = { version = "3.1.0", features = ["derive"] }
|
clap = { version = "3.1.0", features = ["derive"] }
|
||||||
@ -44,10 +40,11 @@ clap_complete_fig = "3.1.0"
|
|||||||
assert_cmd = "2.0.0"
|
assert_cmd = "2.0.0"
|
||||||
rstest = "0.12.0"
|
rstest = "0.12.0"
|
||||||
rstest_reuse = "0.3.0"
|
rstest_reuse = "0.3.0"
|
||||||
|
tempfile = "3.1.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
nix = []
|
nix-dev = []
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
|
@ -3,10 +3,10 @@ use std::io::{self, Write};
|
|||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
|
||||||
use crate::cmd::{Query, Run};
|
use crate::cmd::{Query, Run};
|
||||||
|
use crate::config;
|
||||||
use crate::db::{Database, DatabaseFile};
|
use crate::db::{Database, DatabaseFile};
|
||||||
use crate::error::BrokenPipeHandler;
|
use crate::error::BrokenPipeHandler;
|
||||||
use crate::fzf::Fzf;
|
use crate::util::{self, Fzf};
|
||||||
use crate::{config, util};
|
|
||||||
|
|
||||||
impl Run for Query {
|
impl Run for Query {
|
||||||
fn run(&self) -> Result<()> {
|
fn run(&self) -> Result<()> {
|
||||||
|
@ -3,9 +3,9 @@ use std::io::{self, Write};
|
|||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
|
|
||||||
use crate::cmd::{Remove, Run};
|
use crate::cmd::{Remove, Run};
|
||||||
|
use crate::config;
|
||||||
use crate::db::DatabaseFile;
|
use crate::db::DatabaseFile;
|
||||||
use crate::fzf::Fzf;
|
use crate::util::{self, Fzf};
|
||||||
use crate::{config, util};
|
|
||||||
|
|
||||||
impl Run for Remove {
|
impl Run for Remove {
|
||||||
fn run(&self) -> Result<()> {
|
fn run(&self) -> Result<()> {
|
||||||
|
@ -2,13 +2,14 @@ mod dir;
|
|||||||
mod stream;
|
mod stream;
|
||||||
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::{self, Write};
|
use std::io;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
pub use dir::{Dir, DirList, Epoch, Rank};
|
pub use dir::{Dir, DirList, Epoch, Rank};
|
||||||
pub use stream::Stream;
|
pub use stream::Stream;
|
||||||
use tempfile::{NamedTempFile, PersistError};
|
|
||||||
|
use crate::util;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Database<'file> {
|
pub struct Database<'file> {
|
||||||
@ -24,18 +25,8 @@ impl<'file> Database<'file> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let buffer = self.dirs.to_bytes()?;
|
let buffer = self.dirs.to_bytes()?;
|
||||||
let mut file = NamedTempFile::new_in(self.data_dir)
|
|
||||||
.with_context(|| format!("could not create temporary database in: {}", self.data_dir.display()))?;
|
|
||||||
|
|
||||||
// Preallocate enough space on the file, preventing copying later on. This optimization may
|
|
||||||
// fail on some filesystems, but it is safe to ignore it and proceed.
|
|
||||||
let _ = file.as_file().set_len(buffer.len() as _);
|
|
||||||
file.write_all(&buffer)
|
|
||||||
.with_context(|| format!("could not write to temporary database: {}", file.path().display()))?;
|
|
||||||
|
|
||||||
let path = db_path(&self.data_dir);
|
let path = db_path(&self.data_dir);
|
||||||
persist(file, &path).with_context(|| format!("could not replace database: {}", path.display()))?;
|
util::write(&path, &buffer).context("could not write to database")?;
|
||||||
|
|
||||||
self.modified = false;
|
self.modified = false;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -120,44 +111,6 @@ impl<'file> Database<'file> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
fn persist<P: AsRef<Path>>(file: NamedTempFile, path: P) -> Result<(), PersistError> {
|
|
||||||
file.persist(path)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
fn persist<P: AsRef<Path>>(mut file: NamedTempFile, path: P) -> Result<(), PersistError> {
|
|
||||||
use std::thread;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use rand::distributions::{Distribution, Uniform};
|
|
||||||
use rand::rngs::SmallRng;
|
|
||||||
use rand::SeedableRng;
|
|
||||||
|
|
||||||
// File renames on Windows are not atomic and sometimes fail with `PermissionDenied`. This is
|
|
||||||
// extremely unlikely unless it's running in a loop on multiple threads. Nevertheless, we guard
|
|
||||||
// against it by retrying the rename a fixed number of times.
|
|
||||||
const MAX_TRIES: usize = 10;
|
|
||||||
let mut rng = None;
|
|
||||||
|
|
||||||
for _ in 0..MAX_TRIES {
|
|
||||||
match file.persist(&path) {
|
|
||||||
Ok(_) => break,
|
|
||||||
Err(e) if e.error.kind() == io::ErrorKind::PermissionDenied => {
|
|
||||||
let mut rng = rng.get_or_insert_with(SmallRng::from_entropy);
|
|
||||||
let between = Uniform::from(50..150);
|
|
||||||
let duration = Duration::from_millis(between.sample(&mut rng));
|
|
||||||
thread::sleep(duration);
|
|
||||||
file = e.file;
|
|
||||||
}
|
|
||||||
Err(e) => return Err(e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct DatabaseFile {
|
pub struct DatabaseFile {
|
||||||
buffer: Vec<u8>,
|
buffer: Vec<u8>,
|
||||||
data_dir: PathBuf,
|
data_dir: PathBuf,
|
||||||
|
15
src/error.rs
15
src/error.rs
@ -1,19 +1,20 @@
|
|||||||
|
use std::fmt::{self, Display, Formatter};
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
#[error("could not find fzf, is it installed?")]
|
|
||||||
pub struct FzfNotFound;
|
|
||||||
|
|
||||||
/// Custom error type for early exit.
|
/// Custom error type for early exit.
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug)]
|
||||||
#[error("")]
|
|
||||||
pub struct SilentExit {
|
pub struct SilentExit {
|
||||||
pub code: i32,
|
pub code: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for SilentExit {
|
||||||
|
fn fmt(&self, _: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait BrokenPipeHandler {
|
pub trait BrokenPipeHandler {
|
||||||
fn pipe_exit(self, device: &str) -> Result<()>;
|
fn pipe_exit(self, device: &str) -> Result<()>;
|
||||||
}
|
}
|
||||||
|
77
src/fzf.rs
77
src/fzf.rs
@ -1,77 +0,0 @@
|
|||||||
use std::io::{self, Read};
|
|
||||||
use std::mem;
|
|
||||||
use std::process::{Child, ChildStdin, Stdio};
|
|
||||||
|
|
||||||
use anyhow::{bail, Context, Result};
|
|
||||||
|
|
||||||
use crate::error::{FzfNotFound, SilentExit};
|
|
||||||
use crate::{config, util};
|
|
||||||
|
|
||||||
pub struct Fzf {
|
|
||||||
child: Child,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Fzf {
|
|
||||||
pub fn new(multiple: bool) -> Result<Self> {
|
|
||||||
let bin = if cfg!(windows) { "fzf.exe" } else { "fzf" };
|
|
||||||
let mut command = util::get_command(bin).map_err(|_| FzfNotFound)?;
|
|
||||||
if multiple {
|
|
||||||
command.arg("-m");
|
|
||||||
}
|
|
||||||
command.arg("-n2..").stdin(Stdio::piped()).stdout(Stdio::piped());
|
|
||||||
if let Some(fzf_opts) = config::fzf_opts() {
|
|
||||||
command.env("FZF_DEFAULT_OPTS", fzf_opts);
|
|
||||||
} else {
|
|
||||||
command.args(&[
|
|
||||||
// Search result
|
|
||||||
"--no-sort",
|
|
||||||
// Interface
|
|
||||||
"--keep-right",
|
|
||||||
// Layout
|
|
||||||
"--height=40%",
|
|
||||||
"--info=inline",
|
|
||||||
"--layout=reverse",
|
|
||||||
// Scripting
|
|
||||||
"--exit-0",
|
|
||||||
"--select-1",
|
|
||||||
// Key/Event bindings
|
|
||||||
"--bind=ctrl-z:ignore",
|
|
||||||
]);
|
|
||||||
if cfg!(unix) {
|
|
||||||
command.env("SHELL", "sh");
|
|
||||||
command.arg(r"--preview=\command -p ls -p {2..}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let child = match command.spawn() {
|
|
||||||
Ok(child) => child,
|
|
||||||
Err(e) if e.kind() == io::ErrorKind::NotFound => bail!(FzfNotFound),
|
|
||||||
Err(e) => Err(e).context("could not launch fzf")?,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Fzf { child })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stdin(&mut self) -> &mut ChildStdin {
|
|
||||||
self.child.stdin.as_mut().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn select(mut self) -> Result<String> {
|
|
||||||
// Drop stdin to prevent deadlock.
|
|
||||||
mem::drop(self.child.stdin.take());
|
|
||||||
|
|
||||||
let mut stdout = self.child.stdout.take().unwrap();
|
|
||||||
let mut output = String::new();
|
|
||||||
stdout.read_to_string(&mut output).context("failed to read from fzf")?;
|
|
||||||
|
|
||||||
let status = self.child.wait().context("wait failed on fzf")?;
|
|
||||||
match status.code() {
|
|
||||||
Some(0) => Ok(output),
|
|
||||||
Some(1) => bail!("no match found"),
|
|
||||||
Some(2) => bail!("fzf returned an error"),
|
|
||||||
Some(code @ 130) => bail!(SilentExit { code }),
|
|
||||||
Some(128..=254) | None => bail!("fzf was terminated"),
|
|
||||||
_ => bail!("fzf returned an unknown error"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -8,7 +8,6 @@ mod cmd;
|
|||||||
mod config;
|
mod config;
|
||||||
mod db;
|
mod db;
|
||||||
mod error;
|
mod error;
|
||||||
mod fzf;
|
|
||||||
mod shell;
|
mod shell;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ make_template!(Powershell, "powershell.txt");
|
|||||||
make_template!(Xonsh, "xonsh.txt");
|
make_template!(Xonsh, "xonsh.txt");
|
||||||
make_template!(Zsh, "zsh.txt");
|
make_template!(Zsh, "zsh.txt");
|
||||||
|
|
||||||
#[cfg(feature = "nix")]
|
#[cfg(feature = "nix-dev")]
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use askama::Template;
|
use askama::Template;
|
||||||
|
163
src/util.rs
163
src/util.rs
@ -1,11 +1,172 @@
|
|||||||
use std::env;
|
use std::env;
|
||||||
|
use std::fs::{self, File, OpenOptions};
|
||||||
|
use std::io::{self, Read, Write};
|
||||||
|
use std::mem;
|
||||||
use std::path::{Component, Path, PathBuf};
|
use std::path::{Component, Path, PathBuf};
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
use std::process::{Child, ChildStdin, Stdio};
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{anyhow, bail, Context, Result};
|
||||||
|
|
||||||
|
use crate::config;
|
||||||
use crate::db::Epoch;
|
use crate::db::Epoch;
|
||||||
|
use crate::error::SilentExit;
|
||||||
|
|
||||||
|
pub struct Fzf {
|
||||||
|
child: Child,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Fzf {
|
||||||
|
const ERR_NOT_FOUND: &'static str = "could not find fzf, is it installed?";
|
||||||
|
|
||||||
|
pub fn new(multiple: bool) -> Result<Self> {
|
||||||
|
let bin = if cfg!(windows) { "fzf.exe" } else { "fzf" };
|
||||||
|
let mut command = get_command(bin).map_err(|_| anyhow!(Self::ERR_NOT_FOUND))?;
|
||||||
|
if multiple {
|
||||||
|
command.arg("-m");
|
||||||
|
}
|
||||||
|
command.arg("-n2..").stdin(Stdio::piped()).stdout(Stdio::piped());
|
||||||
|
if let Some(fzf_opts) = config::fzf_opts() {
|
||||||
|
command.env("FZF_DEFAULT_OPTS", fzf_opts);
|
||||||
|
} else {
|
||||||
|
command.args(&[
|
||||||
|
// Search result
|
||||||
|
"--no-sort",
|
||||||
|
// Interface
|
||||||
|
"--keep-right",
|
||||||
|
// Layout
|
||||||
|
"--height=40%",
|
||||||
|
"--info=inline",
|
||||||
|
"--layout=reverse",
|
||||||
|
// Scripting
|
||||||
|
"--exit-0",
|
||||||
|
"--select-1",
|
||||||
|
// Key/Event bindings
|
||||||
|
"--bind=ctrl-z:ignore",
|
||||||
|
]);
|
||||||
|
if cfg!(unix) {
|
||||||
|
command.env("SHELL", "sh");
|
||||||
|
command.arg(r"--preview=\command -p ls -p {2..}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let child = match command.spawn() {
|
||||||
|
Ok(child) => child,
|
||||||
|
Err(e) if e.kind() == io::ErrorKind::NotFound => bail!(Self::ERR_NOT_FOUND),
|
||||||
|
Err(e) => Err(e).context("could not launch fzf")?,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Fzf { child })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stdin(&mut self) -> &mut ChildStdin {
|
||||||
|
self.child.stdin.as_mut().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn select(mut self) -> Result<String> {
|
||||||
|
// Drop stdin to prevent deadlock.
|
||||||
|
mem::drop(self.child.stdin.take());
|
||||||
|
|
||||||
|
let mut stdout = self.child.stdout.take().unwrap();
|
||||||
|
let mut output = String::new();
|
||||||
|
stdout.read_to_string(&mut output).context("failed to read from fzf")?;
|
||||||
|
|
||||||
|
let status = self.child.wait().context("wait failed on fzf")?;
|
||||||
|
match status.code() {
|
||||||
|
Some(0) => Ok(output),
|
||||||
|
Some(1) => bail!("no match found"),
|
||||||
|
Some(2) => bail!("fzf returned an error"),
|
||||||
|
Some(code @ 130) => bail!(SilentExit { code }),
|
||||||
|
Some(128..=254) | None => bail!("fzf was terminated"),
|
||||||
|
_ => bail!("fzf returned an unknown error"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Similar to [`fs::write`], but atomic (best effort on Windows).
|
||||||
|
pub fn write<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
let contents = contents.as_ref();
|
||||||
|
let dir = path.parent().unwrap();
|
||||||
|
|
||||||
|
// Create a tmpfile.
|
||||||
|
let (mut tmp_file, tmp_path) = tmpfile(dir)?;
|
||||||
|
let result = (|| {
|
||||||
|
// Write to the tmpfile.
|
||||||
|
let _ = tmp_file.set_len(contents.len() as u64);
|
||||||
|
tmp_file.write_all(contents).with_context(|| format!("could not write to file: {}", tmp_path.display()))?;
|
||||||
|
|
||||||
|
// Set the owner of the tmpfile (UNIX only).
|
||||||
|
#[cfg(unix)]
|
||||||
|
if let Ok(metadata) = path.metadata() {
|
||||||
|
use nix::unistd::{self, Gid, Uid};
|
||||||
|
use std::os::unix::fs::MetadataExt;
|
||||||
|
use std::os::unix::io::AsRawFd;
|
||||||
|
|
||||||
|
let uid = Uid::from_raw(metadata.uid());
|
||||||
|
let gid = Gid::from_raw(metadata.gid());
|
||||||
|
let _ = unistd::fchown(tmp_file.as_raw_fd(), Some(uid), Some(gid));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close and rename the tmpfile.
|
||||||
|
mem::drop(tmp_file);
|
||||||
|
rename(&tmp_path, path)
|
||||||
|
})();
|
||||||
|
// In case of an error, delete the tmpfile.
|
||||||
|
if result.is_err() {
|
||||||
|
let _ = fs::remove_file(&tmp_path);
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Atomically create a tmpfile in the given directory.
|
||||||
|
fn tmpfile<P: AsRef<Path>>(dir: P) -> Result<(File, PathBuf)> {
|
||||||
|
const MAX_ATTEMPTS: usize = 5;
|
||||||
|
const TMP_NAME_LEN: usize = 16;
|
||||||
|
let dir = dir.as_ref();
|
||||||
|
|
||||||
|
let mut attempts = 0;
|
||||||
|
loop {
|
||||||
|
attempts += 1;
|
||||||
|
|
||||||
|
// Generate a random name for the tmpfile.
|
||||||
|
let mut name = String::with_capacity(TMP_NAME_LEN);
|
||||||
|
name.push_str("tmp_");
|
||||||
|
while name.len() < TMP_NAME_LEN {
|
||||||
|
name.push(fastrand::alphanumeric());
|
||||||
|
}
|
||||||
|
let path = dir.join(name);
|
||||||
|
|
||||||
|
// Atomically create the tmpfile.
|
||||||
|
match OpenOptions::new().write(true).create_new(true).open(&path) {
|
||||||
|
Ok(file) => break Ok((file, path)),
|
||||||
|
Err(e) if e.kind() == io::ErrorKind::AlreadyExists && attempts < MAX_ATTEMPTS => (),
|
||||||
|
Err(e) => break Err(e).with_context(|| format!("could not create file: {}", path.display())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Similar to [`fs::rename`], but retries on Windows.
|
||||||
|
fn rename<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> Result<()> {
|
||||||
|
const MAX_ATTEMPTS: usize = 5;
|
||||||
|
let from = from.as_ref();
|
||||||
|
let to = to.as_ref();
|
||||||
|
|
||||||
|
if cfg!(windows) {
|
||||||
|
let mut attempts = 0;
|
||||||
|
loop {
|
||||||
|
attempts += 1;
|
||||||
|
match fs::rename(from, to) {
|
||||||
|
Err(e) if e.kind() == io::ErrorKind::PermissionDenied && attempts < MAX_ATTEMPTS => (),
|
||||||
|
result => break result,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fs::rename(from, to)
|
||||||
|
}
|
||||||
|
.with_context(|| format!("could not rename file: {} -> {}", from.display(), to.display()))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn canonicalize<P: AsRef<Path>>(path: &P) -> Result<PathBuf> {
|
pub fn canonicalize<P: AsRef<Path>>(path: &P) -> Result<PathBuf> {
|
||||||
dunce::canonicalize(path).with_context(|| format!("could not resolve path: {}", path.as_ref().display()))
|
dunce::canonicalize(path).with_context(|| format!("could not resolve path: {}", path.as_ref().display()))
|
||||||
|
@ -84,8 +84,8 @@ function __zoxide_z_complete
|
|||||||
# If the last argument is empty, use interactive selection.
|
# If the last argument is empty, use interactive selection.
|
||||||
set -l query $tokens[2..-1]
|
set -l query $tokens[2..-1]
|
||||||
set -l result (zoxide query -i -- $query)
|
set -l result (zoxide query -i -- $query)
|
||||||
and commandline -p "$tokens[1] "(string escape $result)
|
commandline --current-process "$tokens[1] "(string escape $result)
|
||||||
commandline -f repaint
|
commandline --function repaint
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
//! Test clap generated completions.
|
//! Test clap generated completions.
|
||||||
#![cfg(feature = "nix")]
|
#![cfg(feature = "nix-dev")]
|
||||||
|
|
||||||
use assert_cmd::Command;
|
use assert_cmd::Command;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user