mirror of
https://github.com/Llewellynvdm/zoxide.git
synced 2024-11-25 14:07:35 +00:00
Add interactive query/remove and import
This commit is contained in:
parent
bc17c25cf6
commit
5c3af59ba6
2
.github/workflows/cargo-clippy.yml
vendored
2
.github/workflows/cargo-clippy.yml
vendored
@ -17,5 +17,5 @@ jobs:
|
||||
components: clippy
|
||||
- uses: actions-rs/clippy-check@v1
|
||||
with:
|
||||
args: --workspace --all-targets --all-features -- -D warnings -D clippy::all
|
||||
args: --all-targets --all-features -- -D warnings -D clippy::all
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
2
.github/workflows/cargo-fmt.yml
vendored
2
.github/workflows/cargo-fmt.yml
vendored
@ -18,4 +18,4 @@ jobs:
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
args: -- --check
|
||||
|
2
.github/workflows/cargo-test.yml
vendored
2
.github/workflows/cargo-test.yml
vendored
@ -25,4 +25,4 @@ jobs:
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --workspace --all-features --no-fail-fast
|
||||
args: --all-features --no-fail-fast
|
||||
|
2
.github/workflows/cargo-udeps.yml
vendored
2
.github/workflows/cargo-udeps.yml
vendored
@ -19,4 +19,4 @@ jobs:
|
||||
crate: cargo-udeps
|
||||
version: latest
|
||||
use-tool-cache: true
|
||||
- run: cargo udeps --all-features --all-targets --workspace
|
||||
- run: cargo udeps --all-features --all-targets
|
||||
|
44
Cargo.lock
generated
44
Cargo.lock
generated
@ -217,9 +217,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.16"
|
||||
version = "0.1.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c30f6d0bc6b00693347368a67d41b58f2fb851215ff1da49e90fe2c5c667151"
|
||||
checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
@ -242,9 +242,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.78"
|
||||
version = "0.2.80"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa7087f49d294270db4e1928fc110c976cd4b9e5a16348e0a1df09afa99e6c98"
|
||||
checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
@ -434,18 +434,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.116"
|
||||
version = "1.0.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96fe57af81d28386a513cbc6858332abc6117cfdb5999647c6444b8f43a370a5"
|
||||
checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.116"
|
||||
version = "1.0.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f630a6370fd8e457873b4bd2ffdae75408bc291ba72be773772a4c2a065d9ae8"
|
||||
checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -460,9 +460,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.45"
|
||||
version = "1.0.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea9c5432ff16d6152371f808fb5a871cd67368171b09bb21b43df8e4a47a3556"
|
||||
checksum = "cc371affeffc477f42a221a1e4297aedcea33d47d19b61455588bd9d8f6b19ac"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -588,32 +588,16 @@ name = "zoxide"
|
||||
version = "0.4.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"askama",
|
||||
"assert_cmd",
|
||||
"bincode",
|
||||
"clap",
|
||||
"dirs-next",
|
||||
"dunce",
|
||||
"glob",
|
||||
"once_cell",
|
||||
"zoxide-engine",
|
||||
"zoxide-shell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zoxide-engine"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
"ordered-float",
|
||||
"rand",
|
||||
"serde",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zoxide-shell"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"askama",
|
||||
"assert_cmd",
|
||||
"once_cell",
|
||||
]
|
||||
|
13
Cargo.toml
13
Cargo.toml
@ -11,15 +11,22 @@ license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.32"
|
||||
askama = { version = "0.10.3", default-features = false }
|
||||
bincode = "1.3.1"
|
||||
clap = "3.0.0-beta.2"
|
||||
dirs-next = "1.0.2"
|
||||
dunce = "1.0.1"
|
||||
glob = "0.3.0"
|
||||
once_cell = "1.4.1"
|
||||
zoxide-engine = { path = "crates/zoxide-engine" }
|
||||
zoxide-shell = { path = "crates/zoxide-shell" }
|
||||
ordered-float = "2.0.0"
|
||||
serde = { version = "1.0.116", features = ["derive"] }
|
||||
tempfile = "3.1.0"
|
||||
|
||||
[workspace]
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
rand = "0.7.3"
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = "1.0.1"
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
|
11
crates/zoxide-engine/.gitignore
vendored
11
crates/zoxide-engine/.gitignore
vendored
@ -1,11 +0,0 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||
Cargo.lock
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "zoxide-engine"
|
||||
version = "0.1.0"
|
||||
authors = ["Ajeet D'Souza <98ajeet@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.33"
|
||||
bincode = "1.3.1"
|
||||
ordered-float = "2.0.0"
|
||||
serde = { version = "1.0.116", features = ["derive"] }
|
||||
tempfile = "3.1.0"
|
@ -1,42 +0,0 @@
|
||||
use crate::query::Query;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Dir {
|
||||
pub path: String,
|
||||
pub rank: Rank,
|
||||
pub last_accessed: Epoch,
|
||||
}
|
||||
|
||||
impl Dir {
|
||||
pub fn is_dir(&self) -> bool {
|
||||
Path::new(&self.path).is_dir()
|
||||
}
|
||||
|
||||
pub fn is_match(&self, query: &Query) -> bool {
|
||||
query.matches(&self.path)
|
||||
}
|
||||
|
||||
pub fn get_score(&self, now: Epoch) -> Rank {
|
||||
const HOUR: Epoch = 60 * 60;
|
||||
const DAY: Epoch = 24 * HOUR;
|
||||
const WEEK: Epoch = 7 * DAY;
|
||||
|
||||
let duration = now.saturating_sub(self.last_accessed);
|
||||
if duration < HOUR {
|
||||
self.rank * 4.0
|
||||
} else if duration < DAY {
|
||||
self.rank * 2.0
|
||||
} else if duration < WEEK {
|
||||
self.rank * 0.5
|
||||
} else {
|
||||
self.rank * 0.25
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type Rank = f64;
|
||||
pub type Epoch = u64;
|
@ -1,7 +0,0 @@
|
||||
pub mod dir;
|
||||
mod query;
|
||||
mod store;
|
||||
|
||||
pub use dir::{Dir, Epoch};
|
||||
pub use query::Query;
|
||||
pub use store::Store;
|
@ -1,43 +0,0 @@
|
||||
use zoxide_engine::Store;
|
||||
|
||||
#[test]
|
||||
fn test_add() {
|
||||
let path = "/foo/bar";
|
||||
let now = 946684800;
|
||||
|
||||
let data_dir = tempfile::tempdir().unwrap();
|
||||
{
|
||||
let mut store = Store::open(data_dir.path()).unwrap();
|
||||
store.add(path, now);
|
||||
store.add(path, now);
|
||||
}
|
||||
{
|
||||
let store = Store::open(data_dir.path()).unwrap();
|
||||
assert_eq!(store.dirs.len(), 1);
|
||||
|
||||
let dir = &store.dirs[0];
|
||||
assert_eq!(dir.path, path);
|
||||
assert_eq!(dir.last_accessed, now);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove() {
|
||||
let path = "/foo/bar";
|
||||
let now = 946684800;
|
||||
|
||||
let data_dir = tempfile::tempdir().unwrap();
|
||||
{
|
||||
let mut store = Store::open(data_dir.path()).unwrap();
|
||||
store.add(path, now);
|
||||
}
|
||||
{
|
||||
let mut store = Store::open(data_dir.path()).unwrap();
|
||||
assert!(store.remove(path));
|
||||
}
|
||||
{
|
||||
let mut store = Store::open(data_dir.path()).unwrap();
|
||||
assert!(store.dirs.is_empty());
|
||||
assert!(!store.remove(path));
|
||||
}
|
||||
}
|
11
crates/zoxide-shell/.gitignore
vendored
11
crates/zoxide-shell/.gitignore
vendored
@ -1,11 +0,0 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||
Cargo.lock
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
@ -1,15 +0,0 @@
|
||||
[package]
|
||||
name = "zoxide-shell"
|
||||
version = "0.1.0"
|
||||
authors = ["Ajeet D'Souza <98ajeet@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.33"
|
||||
askama = { version = "0.10.3", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = "1.0.1"
|
||||
once_cell = "1.4.1"
|
@ -1,101 +0,0 @@
|
||||
use anyhow::{Context, Result};
|
||||
use askama::Template;
|
||||
|
||||
use std::io::Write;
|
||||
use std::ops::Deref;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum Hook {
|
||||
None,
|
||||
Prompt,
|
||||
Pwd,
|
||||
}
|
||||
|
||||
pub trait Generator {
|
||||
fn generate<W: Write>(&self, writer: &mut W) -> Result<()>;
|
||||
}
|
||||
|
||||
impl<T: Template> Generator for T {
|
||||
fn generate<W: Write>(&self, writer: &mut W) -> Result<()> {
|
||||
let source = &self.render().context("could not render template")?;
|
||||
writeln!(writer, "{}", source).context("could not write to output")?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Opts<'a> {
|
||||
pub cmd: Option<&'a str>,
|
||||
pub hook: Hook,
|
||||
pub echo: bool,
|
||||
pub resolve_symlinks: bool,
|
||||
}
|
||||
|
||||
impl Opts<'_> {
|
||||
pub const DEVNULL: &'static str = if cfg!(windows) { "NUL" } else { "/dev/null" };
|
||||
}
|
||||
|
||||
#[derive(Debug, Template)]
|
||||
#[template(path = "bash.txt")]
|
||||
pub struct Bash<'a>(pub &'a Opts<'a>);
|
||||
|
||||
impl<'a> Deref for Bash<'a> {
|
||||
type Target = Opts<'a>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Template)]
|
||||
#[template(path = "fish.txt")]
|
||||
pub struct Fish<'a>(pub &'a Opts<'a>);
|
||||
|
||||
impl<'a> Deref for Fish<'a> {
|
||||
type Target = Opts<'a>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Template)]
|
||||
#[template(path = "posix.txt")]
|
||||
pub struct Posix<'a>(pub &'a Opts<'a>);
|
||||
|
||||
impl<'a> Deref for Posix<'a> {
|
||||
type Target = Opts<'a>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Template)]
|
||||
#[template(path = "powershell.txt")]
|
||||
pub struct PowerShell<'a>(pub &'a Opts<'a>);
|
||||
|
||||
impl<'a> Deref for PowerShell<'a> {
|
||||
type Target = Opts<'a>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Template)]
|
||||
#[template(path = "xonsh.txt")]
|
||||
pub struct Xonsh<'a>(pub &'a Opts<'a>);
|
||||
|
||||
impl<'a> Deref for Xonsh<'a> {
|
||||
type Target = Opts<'a>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Template)]
|
||||
#[template(path = "zsh.txt")]
|
||||
pub struct Zsh<'a>(pub &'a Opts<'a>);
|
||||
|
||||
impl<'a> Deref for Zsh<'a> {
|
||||
type Target = Opts<'a>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.0
|
||||
}
|
||||
}
|
@ -1,181 +0,0 @@
|
||||
use askama::Template;
|
||||
use assert_cmd::Command;
|
||||
use once_cell::sync::OnceCell;
|
||||
use zoxide_shell::{Bash, Fish, Hook, Opts, Posix, PowerShell, Xonsh, Zsh};
|
||||
|
||||
fn opts() -> &'static [Opts<'static>] {
|
||||
static OPTS: OnceCell<Vec<Opts>> = OnceCell::new();
|
||||
OPTS.get_or_init(|| {
|
||||
let mut opts = Vec::new();
|
||||
for &echo in &[false, true] {
|
||||
for &resolve_symlinks in &[false, true] {
|
||||
for &hook in &[Hook::None, Hook::Prompt, Hook::Pwd] {
|
||||
for &cmd in &[None, Some("z")] {
|
||||
opts.push(Opts {
|
||||
echo,
|
||||
resolve_symlinks,
|
||||
hook,
|
||||
cmd,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
opts
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bash() {
|
||||
for opts in opts() {
|
||||
let source = crate::Bash(opts).render().unwrap();
|
||||
Command::new("bash")
|
||||
.args(&["-c", &source])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bash_posix() {
|
||||
for opts in opts() {
|
||||
let source = crate::Posix(opts).render().unwrap();
|
||||
let assert = Command::new("bash")
|
||||
.args(&["--posix", "-c", &source])
|
||||
.assert()
|
||||
.success()
|
||||
.stderr("");
|
||||
|
||||
if opts.hook != Hook::Pwd {
|
||||
assert.stdout("");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dash() {
|
||||
for opts in opts() {
|
||||
let source = crate::Posix(opts).render().unwrap();
|
||||
let assert = Command::new("bash")
|
||||
.args(&["--posix", "-c", &source])
|
||||
.assert()
|
||||
.success()
|
||||
.stderr("");
|
||||
|
||||
if opts.hook != Hook::Pwd {
|
||||
assert.stdout("");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fish() {
|
||||
for opts in opts() {
|
||||
let source = crate::Fish(opts).render().unwrap();
|
||||
Command::new("fish")
|
||||
.args(&["-c", &source])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pwsh() {
|
||||
for opts in opts() {
|
||||
let source = crate::PowerShell(opts).render().unwrap();
|
||||
Command::new("pwsh")
|
||||
.args(&["-c", &source])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_shellcheck_bash() {
|
||||
for opts in opts() {
|
||||
let source = crate::Bash(opts).render().unwrap();
|
||||
Command::new("shellcheck")
|
||||
.args(&["--shell", "bash", "-"])
|
||||
.write_stdin(source.as_bytes())
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_shellcheck_sh() {
|
||||
for opts in opts() {
|
||||
let source = crate::Posix(opts).render().unwrap();
|
||||
Command::new("shellcheck")
|
||||
.args(&["--shell", "sh", "-"])
|
||||
.write_stdin(source.as_bytes())
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_shfmt_bash() {
|
||||
for opts in opts() {
|
||||
let source = crate::Bash(opts).render().unwrap();
|
||||
Command::new("shfmt")
|
||||
.args(&["-d", "-s", "-ln", "bash", "-i", "4", "-ci", "-"])
|
||||
.write_stdin(source.as_bytes())
|
||||
.write_stdin(b"\n".as_ref())
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_shfmt_posix() {
|
||||
for opts in opts() {
|
||||
let source = crate::Posix(opts).render().unwrap();
|
||||
Command::new("shfmt")
|
||||
.args(&["-d", "-s", "-ln", "posix", "-i", "4", "-ci", "-"])
|
||||
.write_stdin(source.as_bytes())
|
||||
.write_stdin(b"\n".as_ref())
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_xonsh() {
|
||||
for opts in opts() {
|
||||
let source = crate::Xonsh(opts).render().unwrap();
|
||||
Command::new("xonsh")
|
||||
.args(&["-c", &source])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_zsh() {
|
||||
for opts in opts() {
|
||||
let source = crate::Zsh(opts).render().unwrap();
|
||||
Command::new("zsh")
|
||||
.args(&["-c", &source])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
}
|
||||
}
|
49
src/cmd/add.rs
Normal file
49
src/cmd/add.rs
Normal file
@ -0,0 +1,49 @@
|
||||
use crate::cmd::Cmd;
|
||||
use crate::config;
|
||||
use crate::util;
|
||||
|
||||
use crate::store::Store;
|
||||
use anyhow::Result;
|
||||
use clap::Clap;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Add a new directory or increment its rank
|
||||
#[derive(Clap, Debug)]
|
||||
pub struct Add {
|
||||
path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl Cmd for Add {
|
||||
fn run(&self) -> Result<()> {
|
||||
let path = match &self.path {
|
||||
Some(path) => {
|
||||
if config::zo_resolve_symlinks() {
|
||||
util::canonicalize(&path)
|
||||
} else {
|
||||
util::resolve_path(&path)
|
||||
}
|
||||
}
|
||||
None => util::current_dir(),
|
||||
}?;
|
||||
|
||||
if config::zo_exclude_dirs()?
|
||||
.iter()
|
||||
.any(|pattern| pattern.matches_path(&path))
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let path = util::path_to_str(&path)?;
|
||||
let now = util::current_time()?;
|
||||
|
||||
let data_dir = config::zo_data_dir()?;
|
||||
let max_age = config::zo_maxage()?;
|
||||
|
||||
let mut store = Store::open(&data_dir)?;
|
||||
store.add(path, now);
|
||||
store.age(max_age);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
51
src/cmd/import.rs
Normal file
51
src/cmd/import.rs
Normal file
@ -0,0 +1,51 @@
|
||||
use crate::cmd::Cmd;
|
||||
use crate::config;
|
||||
use crate::import::{Autojump, Import as _, Z};
|
||||
use crate::util;
|
||||
|
||||
use crate::store::Store;
|
||||
use anyhow::{bail, Result};
|
||||
use clap::{ArgEnum, Clap};
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Import entries from another database
|
||||
#[derive(Clap, Debug)]
|
||||
pub struct Import {
|
||||
path: PathBuf,
|
||||
|
||||
/// Application to import from
|
||||
#[clap(arg_enum, long, default_value = "z")]
|
||||
from: From,
|
||||
|
||||
/// Merge into existing database
|
||||
#[clap(long)]
|
||||
merge: bool,
|
||||
}
|
||||
|
||||
impl Cmd for Import {
|
||||
fn run(&self) -> Result<()> {
|
||||
let data_dir = config::zo_data_dir()?;
|
||||
|
||||
let mut store = Store::open(&data_dir)?;
|
||||
if !self.merge && !store.dirs.is_empty() {
|
||||
bail!("zoxide database is not empty, specify --merge to continue anyway")
|
||||
}
|
||||
|
||||
let resolve_symlinks = config::zo_resolve_symlinks();
|
||||
match self.from {
|
||||
From::Autojump => Autojump {
|
||||
resolve_symlinks,
|
||||
now: util::current_time()?,
|
||||
}
|
||||
.import(&mut store, &self.path),
|
||||
From::Z => Z { resolve_symlinks }.import(&mut store, &self.path),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(ArgEnum, Debug)]
|
||||
enum From {
|
||||
Autojump,
|
||||
Z,
|
||||
}
|
95
src/cmd/init.rs
Normal file
95
src/cmd/init.rs
Normal file
@ -0,0 +1,95 @@
|
||||
use crate::cmd::Cmd;
|
||||
use crate::config;
|
||||
use crate::shell::{self, Hook, Opts};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use askama::Template;
|
||||
use clap::{ArgEnum, Clap};
|
||||
use once_cell::sync::OnceCell;
|
||||
|
||||
/// Generates shell configuration
|
||||
#[derive(Clap, Debug)]
|
||||
#[clap(after_help(env_help()))]
|
||||
pub struct Init {
|
||||
#[clap(arg_enum)]
|
||||
shell: Shell,
|
||||
|
||||
/// Prevents zoxide from defining any commands
|
||||
#[clap(long)]
|
||||
no_aliases: bool,
|
||||
|
||||
/// Renames the 'z' command and corresponding aliases
|
||||
#[clap(long, default_value = "z")]
|
||||
cmd: String,
|
||||
|
||||
/// Chooses event upon which an entry is added to the database
|
||||
#[clap(arg_enum, long, default_value = "pwd")]
|
||||
hook: Hook,
|
||||
}
|
||||
|
||||
impl Cmd for Init {
|
||||
fn run(&self) -> Result<()> {
|
||||
let cmd = if self.no_aliases {
|
||||
None
|
||||
} else {
|
||||
Some(self.cmd.as_str())
|
||||
};
|
||||
|
||||
let echo = config::zo_echo();
|
||||
let resolve_symlinks = config::zo_resolve_symlinks();
|
||||
|
||||
let opts = &Opts {
|
||||
cmd,
|
||||
hook: self.hook,
|
||||
echo,
|
||||
resolve_symlinks,
|
||||
};
|
||||
|
||||
let source = match self.shell {
|
||||
Shell::Bash => shell::Bash(opts).render(),
|
||||
Shell::Fish => shell::Fish(opts).render(),
|
||||
Shell::Posix => shell::Posix(opts).render(),
|
||||
Shell::Powershell => shell::PowerShell(opts).render(),
|
||||
Shell::Xonsh => shell::Xonsh(opts).render(),
|
||||
Shell::Zsh => shell::Zsh(opts).render(),
|
||||
}
|
||||
.context("could not render template")?;
|
||||
println!("{}", source);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(ArgEnum, Debug)]
|
||||
enum Shell {
|
||||
Bash,
|
||||
Fish,
|
||||
Posix,
|
||||
Powershell,
|
||||
Xonsh,
|
||||
Zsh,
|
||||
}
|
||||
|
||||
fn env_help() -> &'static str {
|
||||
static ENV_HELP: OnceCell<String> = OnceCell::new();
|
||||
ENV_HELP.get_or_init(|| {
|
||||
const PATH_SPLIT_SEPARATOR: u8 = if cfg!(any(target_os = "redox", target_os = "windows")) {
|
||||
b';'
|
||||
} else {
|
||||
b':'
|
||||
};
|
||||
|
||||
format!(
|
||||
"\
|
||||
ENVIRONMENT VARIABLES:
|
||||
_ZO_DATA_DIR Path for zoxide data files
|
||||
[current: {data_dir}]
|
||||
_ZO_ECHO Prints the matched directory before navigating to it when set to 1
|
||||
_ZO_EXCLUDE_DIRS List of directory globs to be excluded, separated by '{path_split_separator}'
|
||||
_ZO_FZF_OPTS Custom flags to pass to fzf
|
||||
_ZO_MAXAGE Maximum total age after which entries start getting deleted
|
||||
_ZO_RESOLVE_SYMLINKS Resolve symlinks when storing paths",
|
||||
data_dir=config::zo_data_dir().unwrap_or_else(|_| "none".into()).display(),
|
||||
path_split_separator=PATH_SPLIT_SEPARATOR as char)
|
||||
})
|
||||
}
|
17
src/cmd/mod.rs
Normal file
17
src/cmd/mod.rs
Normal file
@ -0,0 +1,17 @@
|
||||
mod add;
|
||||
mod import;
|
||||
mod init;
|
||||
mod query;
|
||||
mod remove;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
pub use add::Add;
|
||||
pub use import::Import;
|
||||
pub use init::Init;
|
||||
pub use query::Query;
|
||||
pub use remove::Remove;
|
||||
|
||||
pub trait Cmd {
|
||||
fn run(&self) -> Result<()>;
|
||||
}
|
77
src/cmd/query.rs
Normal file
77
src/cmd/query.rs
Normal file
@ -0,0 +1,77 @@
|
||||
use crate::cmd::Cmd;
|
||||
use crate::config;
|
||||
use crate::fzf::Fzf;
|
||||
use crate::util;
|
||||
|
||||
use crate::store::{self, Store};
|
||||
use anyhow::{Context, Result};
|
||||
use clap::Clap;
|
||||
|
||||
use std::io::{self, Write};
|
||||
|
||||
/// Searches for a directory
|
||||
#[derive(Clap, Debug)]
|
||||
pub struct Query {
|
||||
keywords: Vec<String>,
|
||||
|
||||
/// Lists all matching directories
|
||||
#[clap(long, short, conflicts_with = "list")]
|
||||
interactive: bool,
|
||||
|
||||
/// Lists all matching directories
|
||||
#[clap(long, short, conflicts_with = "interactive")]
|
||||
list: bool,
|
||||
|
||||
/// Prints score with results
|
||||
#[clap(long, short)]
|
||||
score: bool,
|
||||
}
|
||||
|
||||
impl Cmd for Query {
|
||||
fn run(&self) -> Result<()> {
|
||||
let data_dir = config::zo_data_dir()?;
|
||||
let mut store = Store::open(&data_dir)?;
|
||||
|
||||
let query = store::Query::new(&self.keywords);
|
||||
let now = util::current_time()?;
|
||||
|
||||
let mut matches = store.iter_matches(&query, now);
|
||||
|
||||
if self.interactive {
|
||||
let mut fzf = Fzf::new()?;
|
||||
let handle = fzf.stdin();
|
||||
for dir in matches {
|
||||
writeln!(handle, "{}", dir.display_score(now)).context("could not write to fzf")?;
|
||||
}
|
||||
let selection = fzf.wait_select()?;
|
||||
if self.score {
|
||||
print!("{}", selection);
|
||||
} else {
|
||||
let path = selection
|
||||
.get(5..)
|
||||
.context("could not read selection from fzf")?;
|
||||
print!("{}", path)
|
||||
}
|
||||
} else if self.list {
|
||||
let stdout = io::stdout();
|
||||
let handle = &mut stdout.lock();
|
||||
for dir in matches {
|
||||
if self.score {
|
||||
writeln!(handle, "{}", dir.display_score(now))
|
||||
} else {
|
||||
writeln!(handle, "{}", dir.display())
|
||||
}
|
||||
.unwrap()
|
||||
}
|
||||
} else {
|
||||
let dir = matches.next().context("no match found")?;
|
||||
if self.score {
|
||||
println!("{}", dir.display_score(now))
|
||||
} else {
|
||||
println!("{}", dir.display())
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
57
src/cmd/remove.rs
Normal file
57
src/cmd/remove.rs
Normal file
@ -0,0 +1,57 @@
|
||||
use crate::cmd::Cmd;
|
||||
use crate::config;
|
||||
use crate::fzf::Fzf;
|
||||
use crate::store::Query;
|
||||
use crate::store::Store;
|
||||
use crate::util;
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
use clap::Clap;
|
||||
|
||||
use std::io::Write;
|
||||
|
||||
/// Removes a directory
|
||||
#[derive(Clap, Debug)]
|
||||
pub struct Remove {
|
||||
#[clap(conflicts_with = "path", long, short, value_name = "keywords")]
|
||||
interactive: Option<Vec<String>>,
|
||||
#[clap(
|
||||
conflicts_with = "interactive",
|
||||
required_unless_present = "interactive"
|
||||
)]
|
||||
path: Option<String>,
|
||||
}
|
||||
|
||||
impl Cmd for Remove {
|
||||
fn run(&self) -> Result<()> {
|
||||
let data_dir = config::zo_data_dir()?;
|
||||
let mut store = Store::open(&data_dir)?;
|
||||
|
||||
let selection;
|
||||
let path = match &self.interactive {
|
||||
Some(keywords) => {
|
||||
let query = Query::new(keywords);
|
||||
let now = util::current_time()?;
|
||||
|
||||
let mut fzf = Fzf::new()?;
|
||||
let handle = fzf.stdin();
|
||||
for dir in store.iter_matches(&query, now) {
|
||||
writeln!(handle, "{}", dir.display_score(now))
|
||||
.context("could not write to fzf")?;
|
||||
}
|
||||
|
||||
selection = fzf.wait_select()?;
|
||||
selection
|
||||
.get(5..selection.len().saturating_sub(1))
|
||||
.context("fzf returned invalid output")?
|
||||
}
|
||||
None => self.path.as_ref().unwrap(),
|
||||
};
|
||||
|
||||
if !store.remove(path) {
|
||||
bail!("path not found in store: {}", &path)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
use crate::store::Rank;
|
||||
use anyhow::{bail, Context, Result};
|
||||
use dirs_next as dirs;
|
||||
use zoxide_engine::dir::Rank;
|
||||
|
||||
use std::env;
|
||||
use std::ffi::OsString;
|
||||
@ -21,6 +21,13 @@ pub fn zo_data_dir() -> Result<PathBuf> {
|
||||
Ok(data_dir)
|
||||
}
|
||||
|
||||
pub fn zo_echo() -> bool {
|
||||
match env::var_os("_ZO_ECHO") {
|
||||
Some(var) => var == "1",
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn zo_exclude_dirs() -> Result<Vec<glob::Pattern>> {
|
||||
match env::var_os("_ZO_EXCLUDE_DIRS") {
|
||||
Some(dirs_osstr) => env::split_paths(&dirs_osstr)
|
||||
@ -55,13 +62,6 @@ pub fn zo_maxage() -> Result<Rank> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn zo_echo() -> bool {
|
||||
match env::var_os("_ZO_ECHO") {
|
||||
Some(var) => var == "1",
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn zo_resolve_symlinks() -> bool {
|
||||
match env::var_os("_ZO_RESOLVE_SYMLINKS") {
|
||||
Some(var) => var == "1",
|
||||
|
12
src/error.rs
Normal file
12
src/error.rs
Normal file
@ -0,0 +1,12 @@
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SilentExit {
|
||||
pub code: i32,
|
||||
}
|
||||
|
||||
impl Display for SilentExit {
|
||||
fn fmt(&self, _: &mut Formatter) -> fmt::Result {
|
||||
Ok(())
|
||||
}
|
||||
}
|
57
src/fzf.rs
Normal file
57
src/fzf.rs
Normal file
@ -0,0 +1,57 @@
|
||||
use crate::config;
|
||||
use crate::error::SilentExit;
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
|
||||
use std::process::{Child, ChildStdin, Command, Stdio};
|
||||
|
||||
pub struct Fzf {
|
||||
child: Child,
|
||||
}
|
||||
|
||||
impl Fzf {
|
||||
pub fn new() -> Result<Self> {
|
||||
let mut command = Command::new("fzf");
|
||||
command
|
||||
.arg("-n2..")
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped());
|
||||
|
||||
if let Some(fzf_opts) = config::zo_fzf_opts() {
|
||||
command.env("FZF_DEFAULT_OPTS", fzf_opts);
|
||||
}
|
||||
|
||||
Ok(Fzf {
|
||||
child: command.spawn().context("could not launch fzf")?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn stdin(&mut self) -> &mut ChildStdin {
|
||||
self.child.stdin.as_mut().unwrap()
|
||||
}
|
||||
|
||||
pub fn wait_select(self) -> Result<String> {
|
||||
let output = self
|
||||
.child
|
||||
.wait_with_output()
|
||||
.context("wait failed on fzf")?;
|
||||
|
||||
match output.status.code() {
|
||||
// normal exit
|
||||
Some(0) => String::from_utf8(output.stdout).context("invalid unicode in fzf output"),
|
||||
|
||||
// no match
|
||||
Some(1) => bail!("no match found"),
|
||||
|
||||
// error
|
||||
Some(2) => bail!("fzf returned an error"),
|
||||
|
||||
// terminated by a signal
|
||||
Some(code @ 130) => bail!(SilentExit { code }),
|
||||
Some(128..=254) | None => bail!("fzf was terminated"),
|
||||
|
||||
// unknown
|
||||
_ => bail!("fzf returned an unknown error"),
|
||||
}
|
||||
}
|
||||
}
|
60
src/import/autojump.rs
Normal file
60
src/import/autojump.rs
Normal file
@ -0,0 +1,60 @@
|
||||
use super::Import;
|
||||
use crate::util;
|
||||
|
||||
use crate::store::{Dir, Epoch, Store};
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::path::Path;
|
||||
|
||||
pub struct Autojump {
|
||||
pub resolve_symlinks: bool,
|
||||
pub now: Epoch,
|
||||
}
|
||||
|
||||
impl Import for Autojump {
|
||||
fn import<P: AsRef<Path>>(&self, store: &mut Store, path: P) -> Result<()> {
|
||||
let file = File::open(path).context("could not open autojump database")?;
|
||||
let reader = BufReader::new(file);
|
||||
|
||||
for (idx, line) in reader.lines().enumerate() {
|
||||
(|| -> Result<()> {
|
||||
let line = line?;
|
||||
if line.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let split_idx = line
|
||||
.find('\t')
|
||||
.with_context(|| format!("invalid entry: {}", line))?;
|
||||
let (rank, path) = line.split_at(split_idx);
|
||||
|
||||
let rank = rank
|
||||
.parse()
|
||||
.with_context(|| format!("invalid rank: {}", rank))?;
|
||||
|
||||
let path = if self.resolve_symlinks {
|
||||
util::canonicalize
|
||||
} else {
|
||||
util::resolve_path
|
||||
}(&path)?;
|
||||
let path = util::path_to_str(&path)?;
|
||||
|
||||
if store.dirs.iter_mut().find(|dir| dir.path == path).is_none() {
|
||||
store.dirs.push(Dir {
|
||||
path: path.into(),
|
||||
rank,
|
||||
last_accessed: self.now,
|
||||
});
|
||||
store.modified = true;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})()
|
||||
.with_context(|| format!("line {}: error reading from z database", idx + 1))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
14
src/import/mod.rs
Normal file
14
src/import/mod.rs
Normal file
@ -0,0 +1,14 @@
|
||||
mod autojump;
|
||||
mod z;
|
||||
|
||||
use crate::store::Store;
|
||||
use anyhow::Result;
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
pub use autojump::Autojump;
|
||||
pub use z::Z;
|
||||
|
||||
pub trait Import {
|
||||
fn import<P: AsRef<Path>>(&self, store: &mut Store, path: P) -> Result<()>;
|
||||
}
|
71
src/import/z.rs
Normal file
71
src/import/z.rs
Normal file
@ -0,0 +1,71 @@
|
||||
use super::Import;
|
||||
use crate::util;
|
||||
|
||||
use crate::store::{Dir, Store};
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::path::Path;
|
||||
|
||||
pub struct Z {
|
||||
pub resolve_symlinks: bool,
|
||||
}
|
||||
|
||||
impl Import for Z {
|
||||
fn import<P: AsRef<Path>>(&self, store: &mut Store, path: P) -> Result<()> {
|
||||
let file = File::open(path).context("could not open z database")?;
|
||||
let reader = BufReader::new(file);
|
||||
|
||||
for (idx, line) in reader.lines().enumerate() {
|
||||
(|| -> Result<()> {
|
||||
let line = line?;
|
||||
if line.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let (path, rank, last_accessed) = (|| {
|
||||
let mut split = line.rsplitn(3, '|');
|
||||
let last_accessed = split.next()?;
|
||||
let rank = split.next()?;
|
||||
let path = split.next()?;
|
||||
Some((path, rank, last_accessed))
|
||||
})()
|
||||
.with_context(|| format!("invalid entry: {}", line))?;
|
||||
|
||||
let path = if self.resolve_symlinks {
|
||||
util::canonicalize
|
||||
} else {
|
||||
util::resolve_path
|
||||
}(&path)?;
|
||||
let path = util::path_to_str(&path)?;
|
||||
|
||||
let rank = rank
|
||||
.parse()
|
||||
.with_context(|| format!("invalid rank: {}", rank))?;
|
||||
|
||||
let last_accessed = last_accessed
|
||||
.parse()
|
||||
.with_context(|| format!("invalid epoch: {}", last_accessed))?;
|
||||
|
||||
match store.dirs.iter_mut().find(|dir| dir.path == path) {
|
||||
Some(dir) => {
|
||||
dir.rank += rank;
|
||||
dir.last_accessed = dir.last_accessed.max(last_accessed);
|
||||
}
|
||||
None => store.dirs.push(Dir {
|
||||
path: path.into(),
|
||||
rank,
|
||||
last_accessed,
|
||||
}),
|
||||
}
|
||||
store.modified = true;
|
||||
|
||||
Ok(())
|
||||
})()
|
||||
.with_context(|| format!("line {}: error reading from z database", idx + 1))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
pub mod config;
|
||||
pub mod util;
|
243
src/main.rs
243
src/main.rs
@ -1,39 +1,20 @@
|
||||
use anyhow::{Context, Result};
|
||||
use clap::{AppSettings, ArgEnum, Clap};
|
||||
use once_cell::sync::OnceCell;
|
||||
use zoxide::{config, util};
|
||||
use zoxide_engine::{Dir, Query, Store};
|
||||
use zoxide_shell::{self as zs, Generator};
|
||||
mod cmd;
|
||||
mod config;
|
||||
mod error;
|
||||
mod fzf;
|
||||
mod import;
|
||||
mod shell;
|
||||
mod store;
|
||||
mod util;
|
||||
|
||||
use std::io::{self, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use crate::cmd::{Add, Cmd, Import, Init, Query, Remove};
|
||||
use crate::error::SilentExit;
|
||||
|
||||
fn env_help() -> &'static str {
|
||||
static ENV_HELP: OnceCell<String> = OnceCell::new();
|
||||
ENV_HELP.get_or_init(|| {
|
||||
const PATH_SPLIT_SEPARATOR: u8 = if cfg!(any(target_os = "redox", target_os = "windows")) {
|
||||
b';'
|
||||
} else {
|
||||
b':'
|
||||
};
|
||||
use anyhow::Result;
|
||||
use clap::{AppSettings, Clap};
|
||||
|
||||
format!(
|
||||
"\
|
||||
ENVIRONMENT VARIABLES:
|
||||
_ZO_DATA_DIR Path for zoxide data files
|
||||
[current: {data_dir}]
|
||||
_ZO_ECHO Prints the matched directory before navigating to it when set to 1
|
||||
_ZO_EXCLUDE_DIRS List of directory globs to be excluded, separated by '{split_paths_separator}'
|
||||
_ZO_FZF_OPTS Custom flags to pass to fzf
|
||||
_ZO_MAXAGE Maximum total age after which entries start getting deleted
|
||||
_ZO_RESOLVE_SYMLINKS Resolve symlinks when storing paths",
|
||||
data_dir=config::zo_data_dir().unwrap_or_else(|_| "none".into()).display(),
|
||||
split_paths_separator=PATH_SPLIT_SEPARATOR as char)
|
||||
})
|
||||
}
|
||||
use std::process;
|
||||
|
||||
// TODO: import
|
||||
// TODO: query interactive
|
||||
#[derive(Debug, Clap)]
|
||||
#[clap(
|
||||
about,
|
||||
@ -42,191 +23,27 @@ ENVIRONMENT VARIABLES:
|
||||
global_setting(AppSettings::VersionlessSubcommands),
|
||||
version = env!("ZOXIDE_VERSION"))]
|
||||
enum Opts {
|
||||
/// Adds a new directory or increments its rank
|
||||
Add { path: Option<PathBuf> },
|
||||
|
||||
/// Generates shell configuration
|
||||
#[clap(after_help(env_help()))]
|
||||
Init {
|
||||
#[clap(arg_enum)]
|
||||
shell: Shell,
|
||||
|
||||
/// Prevents zoxide from defining any commands
|
||||
#[clap(long)]
|
||||
no_aliases: bool,
|
||||
|
||||
/// Renames the 'z' command and corresponding aliases
|
||||
#[clap(long, default_value = "z")]
|
||||
cmd: String,
|
||||
|
||||
/// Chooses event upon which an entry is added to the database
|
||||
#[clap(arg_enum, long, default_value = "pwd")]
|
||||
hook: Hook,
|
||||
},
|
||||
|
||||
/// Searches for a directory
|
||||
Query {
|
||||
keywords: Vec<String>,
|
||||
|
||||
/// Lists all matching directories
|
||||
#[clap(long, short)]
|
||||
list: bool,
|
||||
|
||||
/// Prints score with results
|
||||
#[clap(long, short)]
|
||||
score: bool,
|
||||
},
|
||||
|
||||
/// Removes a directory
|
||||
Remove { path: String },
|
||||
}
|
||||
|
||||
#[derive(ArgEnum, Debug)]
|
||||
enum Shell {
|
||||
Bash,
|
||||
Fish,
|
||||
Posix,
|
||||
Powershell,
|
||||
Xonsh,
|
||||
Zsh,
|
||||
}
|
||||
|
||||
#[derive(ArgEnum, Debug)]
|
||||
enum Hook {
|
||||
None,
|
||||
Prompt,
|
||||
Pwd,
|
||||
Add(Add),
|
||||
Import(Import),
|
||||
Init(Init),
|
||||
Query(Query),
|
||||
Remove(Remove),
|
||||
}
|
||||
|
||||
pub fn main() -> Result<()> {
|
||||
let opts = Opts::parse();
|
||||
|
||||
match opts {
|
||||
Opts::Add { path } => {
|
||||
let path = match path {
|
||||
Some(path) => {
|
||||
if config::zo_resolve_symlinks() {
|
||||
util::canonicalize(&path)
|
||||
} else {
|
||||
util::resolve_path(&path)
|
||||
}
|
||||
}
|
||||
None => util::current_dir(),
|
||||
}?;
|
||||
let result: Result<()> = match opts {
|
||||
Opts::Add(cmd) => cmd.run(),
|
||||
Opts::Import(cmd) => cmd.run(),
|
||||
Opts::Init(cmd) => cmd.run(),
|
||||
Opts::Query(cmd) => cmd.run(),
|
||||
Opts::Remove(cmd) => cmd.run(),
|
||||
};
|
||||
|
||||
if config::zo_exclude_dirs()?
|
||||
.iter()
|
||||
.any(|pattern| pattern.matches_path(&path))
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let path = util::path_to_str(&path)?;
|
||||
let now = util::current_time()?;
|
||||
|
||||
let data_dir = config::zo_data_dir()?;
|
||||
let max_age = config::zo_maxage()?;
|
||||
|
||||
let mut store = Store::open(&data_dir)?;
|
||||
store.add(path, now);
|
||||
store.age(max_age);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Opts::Init {
|
||||
shell,
|
||||
no_aliases,
|
||||
cmd,
|
||||
hook,
|
||||
} => {
|
||||
let cmd = if no_aliases { None } else { Some(cmd.as_str()) };
|
||||
|
||||
let hook = match hook {
|
||||
Hook::None => zs::Hook::None,
|
||||
Hook::Prompt => zs::Hook::Prompt,
|
||||
Hook::Pwd => zs::Hook::Pwd,
|
||||
};
|
||||
|
||||
let echo = config::zo_echo();
|
||||
let resolve_symlinks = config::zo_resolve_symlinks();
|
||||
|
||||
let opts = &zs::Opts {
|
||||
cmd,
|
||||
hook,
|
||||
echo,
|
||||
resolve_symlinks,
|
||||
};
|
||||
|
||||
let stdout = io::stdout();
|
||||
let handle = &mut stdout.lock();
|
||||
|
||||
match shell {
|
||||
Shell::Bash => zs::Bash(opts).generate(handle),
|
||||
Shell::Fish => zs::Fish(opts).generate(handle),
|
||||
Shell::Posix => zs::Posix(opts).generate(handle),
|
||||
Shell::Powershell => zs::PowerShell(opts).generate(handle),
|
||||
Shell::Xonsh => zs::Xonsh(opts).generate(handle),
|
||||
Shell::Zsh => zs::Zsh(opts).generate(handle),
|
||||
}?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Opts::Query {
|
||||
keywords,
|
||||
list,
|
||||
score,
|
||||
} => {
|
||||
let data_dir = config::zo_data_dir()?;
|
||||
let mut store = Store::open(&data_dir)?;
|
||||
|
||||
let query = Query::new(&keywords);
|
||||
let now = util::current_time()?;
|
||||
|
||||
let stdout = io::stdout();
|
||||
let mut handle = stdout.lock();
|
||||
|
||||
let mut print_dir = |dir: &Dir| {
|
||||
if score {
|
||||
let dir_score = dir.get_score(now);
|
||||
let dir_score_clamped = if dir_score > 9999.0 {
|
||||
9999
|
||||
} else if dir_score > 0.0 {
|
||||
dir_score as _
|
||||
} else {
|
||||
0
|
||||
};
|
||||
writeln!(&mut handle, "{:>4} {}", dir_score_clamped, dir.path)
|
||||
} else {
|
||||
writeln!(&mut handle, "{}", dir.path)
|
||||
}
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
let mut matches = store
|
||||
.iter_matches(&query, now)
|
||||
.filter(|dir| Path::new(&dir.path).is_dir());
|
||||
|
||||
if list {
|
||||
for dir in matches {
|
||||
print_dir(dir);
|
||||
}
|
||||
} else {
|
||||
let dir = matches.next().context("no match found")?;
|
||||
print_dir(dir);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Opts::Remove { path } => {
|
||||
let data_dir = config::zo_data_dir()?;
|
||||
|
||||
let mut store = Store::open(&data_dir)?;
|
||||
store.remove(path);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
result.map_err(|e| match e.downcast::<SilentExit>() {
|
||||
Ok(SilentExit { code }) => process::exit(code),
|
||||
// TODO: change the error prefix to `zoxide:`
|
||||
Err(e) => e,
|
||||
})
|
||||
}
|
||||
|
234
src/shell.rs
Normal file
234
src/shell.rs
Normal file
@ -0,0 +1,234 @@
|
||||
use clap::ArgEnum;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Opts<'a> {
|
||||
pub cmd: Option<&'a str>,
|
||||
pub hook: Hook,
|
||||
pub echo: bool,
|
||||
pub resolve_symlinks: bool,
|
||||
}
|
||||
|
||||
impl Opts<'_> {
|
||||
pub const DEVNULL: &'static str = if cfg!(windows) { "NUL" } else { "/dev/null" };
|
||||
}
|
||||
|
||||
macro_rules! make_template {
|
||||
($name:ident, $path:expr) => {
|
||||
#[derive(::std::fmt::Debug, ::askama::Template)]
|
||||
#[template(path = $path)]
|
||||
pub struct $name<'a>(pub &'a self::Opts<'a>);
|
||||
|
||||
impl<'a> ::std::ops::Deref for $name<'a> {
|
||||
type Target = self::Opts<'a>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
make_template!(Bash, "bash.txt");
|
||||
make_template!(Fish, "fish.txt");
|
||||
make_template!(Posix, "posix.txt");
|
||||
make_template!(PowerShell, "powershell.txt");
|
||||
make_template!(Xonsh, "xonsh.txt");
|
||||
make_template!(Zsh, "zsh.txt");
|
||||
|
||||
#[derive(ArgEnum, Clone, Copy, Debug, PartialEq)]
|
||||
pub enum Hook {
|
||||
None,
|
||||
Prompt,
|
||||
Pwd,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use askama::Template;
|
||||
use assert_cmd::Command;
|
||||
use once_cell::sync::OnceCell;
|
||||
|
||||
fn opts() -> &'static [Opts<'static>] {
|
||||
static OPTS: OnceCell<Vec<Opts>> = OnceCell::new();
|
||||
OPTS.get_or_init(|| {
|
||||
const BOOLS: &[bool] = &[false, true];
|
||||
const HOOKS: &[Hook] = &[Hook::None, Hook::Prompt, Hook::Pwd];
|
||||
const CMDS: &[Option<&str>] = &[None, Some("z")];
|
||||
|
||||
let mut opts = Vec::new();
|
||||
for &echo in BOOLS {
|
||||
for &resolve_symlinks in BOOLS {
|
||||
for &hook in HOOKS {
|
||||
for &cmd in CMDS {
|
||||
let opt = Opts {
|
||||
echo,
|
||||
resolve_symlinks,
|
||||
hook,
|
||||
cmd,
|
||||
};
|
||||
opts.push(opt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
opts
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bash() {
|
||||
for opts in opts() {
|
||||
let source = Bash(opts).render().unwrap();
|
||||
Command::new("bash")
|
||||
.args(&["-c", &source])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bash_posix() {
|
||||
for opts in opts() {
|
||||
let source = Posix(opts).render().unwrap();
|
||||
let assert = Command::new("bash")
|
||||
.args(&["--posix", "-c", &source])
|
||||
.assert()
|
||||
.success()
|
||||
.stderr("");
|
||||
|
||||
if opts.hook != Hook::Pwd {
|
||||
assert.stdout("");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dash() {
|
||||
for opts in opts() {
|
||||
let source = Posix(opts).render().unwrap();
|
||||
let assert = Command::new("bash")
|
||||
.args(&["--posix", "-c", &source])
|
||||
.assert()
|
||||
.success()
|
||||
.stderr("");
|
||||
|
||||
if opts.hook != Hook::Pwd {
|
||||
assert.stdout("");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fish() {
|
||||
for opts in opts() {
|
||||
let source = Fish(opts).render().unwrap();
|
||||
Command::new("fish")
|
||||
.args(&["-c", &source])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pwsh() {
|
||||
for opts in opts() {
|
||||
let source = PowerShell(opts).render().unwrap();
|
||||
Command::new("pwsh")
|
||||
.args(&["-c", &source])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_shellcheck_bash() {
|
||||
for opts in opts() {
|
||||
let source = Bash(opts).render().unwrap();
|
||||
Command::new("shellcheck")
|
||||
.args(&["--shell", "bash", "-"])
|
||||
.write_stdin(source.as_bytes())
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_shellcheck_sh() {
|
||||
for opts in opts() {
|
||||
let source = Posix(opts).render().unwrap();
|
||||
Command::new("shellcheck")
|
||||
.args(&["--shell", "sh", "-"])
|
||||
.write_stdin(source.as_bytes())
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_shfmt_bash() {
|
||||
for opts in opts() {
|
||||
let source = Bash(opts).render().unwrap();
|
||||
Command::new("shfmt")
|
||||
.args(&["-d", "-s", "-ln", "bash", "-i", "4", "-ci", "-"])
|
||||
.write_stdin(source.as_bytes())
|
||||
.write_stdin(b"\n".as_ref())
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_shfmt_posix() {
|
||||
for opts in opts() {
|
||||
let source = Posix(opts).render().unwrap();
|
||||
Command::new("shfmt")
|
||||
.args(&["-d", "-s", "-ln", "posix", "-i", "4", "-ci", "-"])
|
||||
.write_stdin(source.as_bytes())
|
||||
.write_stdin(b"\n".as_ref())
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_xonsh() {
|
||||
for opts in opts() {
|
||||
let source = Xonsh(opts).render().unwrap();
|
||||
Command::new("xonsh")
|
||||
.args(&["-c", &source])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_zsh() {
|
||||
for opts in opts() {
|
||||
let source = Zsh(opts).render().unwrap();
|
||||
Command::new("zsh")
|
||||
.args(&["-c", &source])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
}
|
||||
}
|
||||
}
|
73
src/store/dir.rs
Normal file
73
src/store/dir.rs
Normal file
@ -0,0 +1,73 @@
|
||||
use super::{Epoch, Query, Rank};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Dir {
|
||||
pub path: String,
|
||||
pub rank: Rank,
|
||||
pub last_accessed: Epoch,
|
||||
}
|
||||
|
||||
impl Dir {
|
||||
pub fn is_match(&self, query: &Query) -> bool {
|
||||
query.matches(&self.path) && Path::new(&self.path).is_dir()
|
||||
}
|
||||
|
||||
pub fn get_score(&self, now: Epoch) -> Rank {
|
||||
const HOUR: Epoch = 60 * 60;
|
||||
const DAY: Epoch = 24 * HOUR;
|
||||
const WEEK: Epoch = 7 * DAY;
|
||||
|
||||
let duration = now.saturating_sub(self.last_accessed);
|
||||
if duration < HOUR {
|
||||
self.rank * 4.0
|
||||
} else if duration < DAY {
|
||||
self.rank * 2.0
|
||||
} else if duration < WEEK {
|
||||
self.rank * 0.5
|
||||
} else {
|
||||
self.rank * 0.25
|
||||
}
|
||||
}
|
||||
|
||||
pub fn display(&self) -> DirDisplay {
|
||||
DirDisplay { dir: self }
|
||||
}
|
||||
|
||||
pub fn display_score(&self, now: Epoch) -> DirDisplayScore {
|
||||
DirDisplayScore { dir: self, now }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DirDisplay<'a> {
|
||||
dir: &'a Dir,
|
||||
}
|
||||
|
||||
impl Display for DirDisplay<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.dir.path)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DirDisplayScore<'a> {
|
||||
dir: &'a Dir,
|
||||
now: Epoch,
|
||||
}
|
||||
|
||||
impl Display for DirDisplayScore<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
let score = self.dir.get_score(self.now);
|
||||
let score = if score > 9999.0 {
|
||||
9999
|
||||
} else if score > 0.0 {
|
||||
score as _
|
||||
} else {
|
||||
0
|
||||
};
|
||||
write!(f, "{:>4} {}", score, self.dir.path)
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
use crate::dir::{Dir, Epoch, Rank};
|
||||
use crate::query::Query;
|
||||
mod dir;
|
||||
mod query;
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
use bincode::Options;
|
||||
@ -12,6 +12,12 @@ use std::fs;
|
||||
use std::io::{self, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub use dir::Dir;
|
||||
pub use query::Query;
|
||||
|
||||
pub type Rank = f64;
|
||||
pub type Epoch = u64;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Store {
|
||||
pub dirs: Vec<Dir>,
|
||||
@ -199,28 +205,84 @@ impl Drop for Store {
|
||||
#[derive(Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct StoreVersion(pub u32);
|
||||
|
||||
#[cfg(windows)]
|
||||
fn persist<P: AsRef<Path>>(mut file: NamedTempFile, path: P) -> Result<(), PersistError> {
|
||||
if cfg!(windows) {
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use rand::distributions::{Distribution, Uniform};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
// 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;
|
||||
for _ in 0..MAX_TRIES {
|
||||
match file.persist(&path) {
|
||||
Ok(_) => break,
|
||||
Err(e) if e.error.kind() == io::ErrorKind::PermissionDenied => {
|
||||
file = e.file;
|
||||
thread::sleep(Duration::from_millis(50));
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
// 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(rand::thread_rng);
|
||||
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),
|
||||
}
|
||||
} else {
|
||||
file.persist(&path)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn persist<P: AsRef<Path>>(file: NamedTempFile, path: P) -> Result<(), PersistError> {
|
||||
file.persist(&path)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_add() {
|
||||
let path = "/foo/bar";
|
||||
let now = 946684800;
|
||||
|
||||
let data_dir = tempfile::tempdir().unwrap();
|
||||
{
|
||||
let mut store = Store::open(data_dir.path()).unwrap();
|
||||
store.add(path, now);
|
||||
store.add(path, now);
|
||||
}
|
||||
{
|
||||
let store = Store::open(data_dir.path()).unwrap();
|
||||
assert_eq!(store.dirs.len(), 1);
|
||||
|
||||
let dir = &store.dirs[0];
|
||||
assert_eq!(dir.path, path);
|
||||
assert_eq!(dir.last_accessed, now);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove() {
|
||||
let path = "/foo/bar";
|
||||
let now = 946684800;
|
||||
|
||||
let data_dir = tempfile::tempdir().unwrap();
|
||||
{
|
||||
let mut store = Store::open(data_dir.path()).unwrap();
|
||||
store.add(path, now);
|
||||
}
|
||||
{
|
||||
let mut store = Store::open(data_dir.path()).unwrap();
|
||||
assert!(store.remove(path));
|
||||
}
|
||||
{
|
||||
let mut store = Store::open(data_dir.path()).unwrap();
|
||||
assert!(store.dirs.is_empty());
|
||||
assert!(!store.remove(path));
|
||||
}
|
||||
}
|
||||
}
|
27
src/util.rs
27
src/util.rs
@ -1,4 +1,4 @@
|
||||
use zoxide_engine::Epoch;
|
||||
use crate::store::Epoch;
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
|
||||
@ -35,7 +35,6 @@ pub fn path_to_str<P: AsRef<Path>>(path: &P) -> Result<&str> {
|
||||
/// If path is already absolute, the path is still processed to be cleaned, as it can contained ".." or "." (or other)
|
||||
/// character.
|
||||
/// If path is relative, use the current directory to build the absolute path.
|
||||
#[cfg(any(unix, windows))]
|
||||
pub fn resolve_path<P: AsRef<Path>>(path: &P) -> Result<PathBuf> {
|
||||
let path = path.as_ref();
|
||||
let base_path;
|
||||
@ -44,18 +43,7 @@ pub fn resolve_path<P: AsRef<Path>>(path: &P) -> Result<PathBuf> {
|
||||
let mut stack = Vec::new();
|
||||
|
||||
// initialize root
|
||||
if cfg!(unix) {
|
||||
match components.peek() {
|
||||
Some(Component::RootDir) => {
|
||||
let root = components.next().unwrap();
|
||||
stack.push(root);
|
||||
}
|
||||
_ => {
|
||||
base_path = current_dir()?;
|
||||
stack.extend(base_path.components());
|
||||
}
|
||||
}
|
||||
} else if cfg!(windows) {
|
||||
if cfg!(windows) {
|
||||
use std::path::Prefix;
|
||||
|
||||
fn get_drive_letter<P: AsRef<Path>>(path: P) -> Option<u8> {
|
||||
@ -133,6 +121,17 @@ pub fn resolve_path<P: AsRef<Path>>(path: &P) -> Result<PathBuf> {
|
||||
stack.extend(base_path.components());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match components.peek() {
|
||||
Some(Component::RootDir) => {
|
||||
let root = components.next().unwrap();
|
||||
stack.push(root);
|
||||
}
|
||||
_ => {
|
||||
base_path = current_dir()?;
|
||||
stack.extend(base_path.components());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for component in components {
|
||||
|
@ -74,6 +74,8 @@ function __zoxide_z() {
|
||||
echo "zoxide: \\$OLDPWD is not set"
|
||||
return 1
|
||||
fi
|
||||
elif [ "$#" -eq 1 ] && [ -d "$1" ]; then
|
||||
__zoxide_cd "$1"
|
||||
else
|
||||
local __zoxide_result
|
||||
__zoxide_result="$(zoxide query -- "$@")" && __zoxide_cd "$__zoxide_result"
|
||||
@ -108,8 +110,7 @@ function __zoxide_zr() {
|
||||
|
||||
# Remove an entry from the database using interactive selection.
|
||||
function __zoxide_zri() {
|
||||
local __zoxide_result
|
||||
__zoxide_result="$(zoxide query -i -- "$@")" && zoxide remove "$__zoxide_result"
|
||||
zoxide remove -i "$@"
|
||||
}
|
||||
|
||||
{{ SECTION }}
|
@ -56,6 +56,8 @@ function __zoxide_z
|
||||
__zoxide_cd $HOME
|
||||
else if begin; test $argc -eq 1; and test $argv[1] = '-'; end
|
||||
__zoxide_cd -
|
||||
else if begin; test $argc -eq 1; and test -d $argv[1]; end
|
||||
__zoxide_cd $argv[1]
|
||||
else
|
||||
set -l __zoxide_result (zoxide query -- $argv)
|
||||
and __zoxide_cd $__zoxide_result
|
||||
@ -90,8 +92,7 @@ end
|
||||
|
||||
# Remove an entry from the database using interactive selection.
|
||||
function __zoxide_zri
|
||||
set -l __zoxide_result (zoxide query -i -- $argv)
|
||||
and zoxide remove $__zoxide_result
|
||||
zoxide remove -i $argv
|
||||
end
|
||||
|
||||
{{ SECTION }}
|
@ -77,6 +77,8 @@ __zoxide_z() {
|
||||
echo "zoxide: \\$OLDPWD is not set"
|
||||
return 1
|
||||
fi
|
||||
elif [ "$#" -eq 1 ] && [ -d "$1" ]; then
|
||||
__zoxide_cd "$1"
|
||||
else
|
||||
__zoxide_result="$(zoxide query -- "$@")" && __zoxide_cd "$__zoxide_result"
|
||||
fi
|
||||
@ -109,7 +111,7 @@ __zoxide_zr() {
|
||||
|
||||
# Remove an entry from the database using interactive selection.
|
||||
__zoxide_zri() {
|
||||
__zoxide_result="$(zoxide query -i -- "$@")" && zoxide remove "$__zoxide_result"
|
||||
zoxide remove -i "$@"
|
||||
}
|
||||
|
||||
{{ SECTION }}
|
@ -65,6 +65,9 @@ function __zoxide_z {
|
||||
elseif ($args.Length -eq 1 -and $args[0] -eq '-') {
|
||||
__zoxide_cd -
|
||||
}
|
||||
elseif ($args.Length -eq 1 -and ( Test-Path $args[0] -PathType Container) ) {
|
||||
__zoxide_cd $args[0]
|
||||
}
|
||||
else {
|
||||
$__zoxide_result = zoxide query -- @args
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
@ -74,7 +77,7 @@ function __zoxide_z {
|
||||
}
|
||||
|
||||
# Jump to a directory using interactive search.
|
||||
function zi {
|
||||
function __zoxide_zi {
|
||||
$__zoxide_result = zoxide query -i -- @args
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
__zoxide_cd $__zoxide_result
|
||||
@ -103,10 +106,7 @@ function __zoxide_zr {
|
||||
|
||||
# Remove an entry from the database using interactive selection.
|
||||
function __zoxide_zri {
|
||||
$_zoxide_result = zoxide query -i -- @args
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
zoxide remove $_zoxide_result
|
||||
}
|
||||
zoxide remove -i @args
|
||||
}
|
||||
|
||||
{{ SECTION }}
|
||||
@ -136,11 +136,4 @@ Set-Alias {{cmd}}ri __zoxide_zri
|
||||
# To initialize zoxide with PowerShell, add the following line to your
|
||||
# PowerShell configuration file (the location is stored in $profile):
|
||||
#
|
||||
# Invoke-Expression (& {
|
||||
# $hook = if ($PSVersionTable.PSVersion.Major -ge 6) {
|
||||
# 'pwd'
|
||||
# } else {
|
||||
# 'prompt'
|
||||
# }
|
||||
# (zoxide init powershell --hook $hook) -join "`n"
|
||||
# })
|
||||
# Invoke-Expression (& { $hook = if ($PSVersionTable.PSVersion.Major -ge 6) { 'pwd' } else { 'prompt' } (zoxide init powershell --hook $hook) -join "`n" })
|
@ -105,19 +105,8 @@ def __zoxide_zr(args: [str]):
|
||||
zoxide remove @(args)
|
||||
|
||||
# Remove an entry from the database using interactive selection.
|
||||
def __zoxide_zri(keywords: [str]):
|
||||
try:
|
||||
__zoxide_cmd = subprocess.run(["zoxide", "query", "--"] + keywords, check=True, stdout=subprocess.PIPE)
|
||||
except CalledProcessError as e:
|
||||
return e.returncode
|
||||
|
||||
try:
|
||||
__zoxide_result = __zoxide_cmd.stdout[:-1].decode("utf-8")
|
||||
except UnicodeDecodeError:
|
||||
print(f"zoxide: invalid unicode in result: {__zoxide_result}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
zoxide remove @(__zoxide_result)
|
||||
def __zoxide_zri(args: [str]):
|
||||
zoxide remove -i @(args)
|
||||
|
||||
{{ SECTION }}
|
||||
# Convenient aliases for zoxide. Disable these using --no-aliases.
|
@ -95,8 +95,7 @@ function __zoxide_zr() {
|
||||
|
||||
# Remove an entry from the database using interactive selection.
|
||||
function __zoxide_zri() {
|
||||
local __zoxide_result
|
||||
__zoxide_result="$(zoxide query -i -- "$@")" && zoxide remove "$__zoxide_result"
|
||||
zoxide remove -i "$@"
|
||||
}
|
||||
|
||||
{{ SECTION }}
|
Loading…
Reference in New Issue
Block a user