This commit is contained in:
w3irdrobot 2025-10-08 01:06:20 +00:00 committed by GitHub
commit 2fd3a8afa3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 114 additions and 17 deletions

29
Cargo.lock generated
View File

@ -704,6 +704,15 @@ version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "semver"
version = "1.0.26"
@ -831,6 +840,16 @@ dependencies = [
"libc",
]
[[package]]
name = "walkdir"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
@ -858,6 +877,15 @@ dependencies = [
"winsafe",
]
[[package]]
name = "winapi-util"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
@ -1004,5 +1032,6 @@ dependencies = [
"rstest_reuse",
"serde",
"tempfile",
"walkdir",
"which",
]

View File

@ -30,6 +30,7 @@ fastrand = "2.0.0"
glob = "0.3.0"
ouroboros = "0.18.3"
serde = { version = "1.0.116", features = ["derive"] }
walkdir = "2.5.0"
[target.'cfg(unix)'.dependencies]
nix = { version = "0.30.1", default-features = false, features = [

View File

@ -32,6 +32,8 @@ _zoxide() {
_arguments "${_arguments_options[@]}" : \
'-s+[The rank to increment the entry if it exists or initialize it with if it doesn'\''t]:SCORE:_default' \
'--score=[The rank to increment the entry if it exists or initialize it with if it doesn'\''t]:SCORE:_default' \
'-r[Recursively add directories]' \
'--recursive[Recursively add directories]' \
'-h[Print help]' \
'--help[Print help]' \
'-V[Print version]' \
@ -138,6 +140,8 @@ _arguments "${_arguments_options[@]}" : \
;;
(remove)
_arguments "${_arguments_options[@]}" : \
'-r[Recursively remove directories]' \
'--recursive[Recursively remove directories]' \
'-h[Print help]' \
'--help[Print help]' \
'-V[Print version]' \

View File

@ -36,6 +36,8 @@ Register-ArgumentCompleter -Native -CommandName 'zoxide' -ScriptBlock {
'zoxide;add' {
[CompletionResult]::new('-s', '-s', [CompletionResultType]::ParameterName, 'The rank to increment the entry if it exists or initialize it with if it doesn''t')
[CompletionResult]::new('--score', '--score', [CompletionResultType]::ParameterName, 'The rank to increment the entry if it exists or initialize it with if it doesn''t')
[CompletionResult]::new('-r', '-r', [CompletionResultType]::ParameterName, 'Recursively add directories')
[CompletionResult]::new('--recursive', '--recursive', [CompletionResultType]::ParameterName, 'Recursively add directories')
[CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version')
@ -118,6 +120,8 @@ Register-ArgumentCompleter -Native -CommandName 'zoxide' -ScriptBlock {
break
}
'zoxide;remove' {
[CompletionResult]::new('-r', '-r', [CompletionResultType]::ParameterName, 'Recursively remove directories')
[CompletionResult]::new('--recursive', '--recursive', [CompletionResultType]::ParameterName, 'Recursively remove directories')
[CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version')

View File

@ -67,7 +67,7 @@ _zoxide() {
return 0
;;
zoxide__add)
opts="-s -h -V --score --help --version <PATHS>..."
opts="-s -r -h -V --score --recursive --help --version <PATHS>..."
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
@ -227,7 +227,7 @@ _zoxide() {
return 0
;;
zoxide__remove)
opts="-h -V --help --version [PATHS]..."
opts="-r -h -V --recursive --help --version [PATHS]..."
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0

View File

@ -32,6 +32,8 @@ set edit:completion:arg-completer[zoxide] = {|@words|
&'zoxide;add'= {
cand -s 'The rank to increment the entry if it exists or initialize it with if it doesn''t'
cand --score 'The rank to increment the entry if it exists or initialize it with if it doesn''t'
cand -r 'Recursively add directories'
cand --recursive 'Recursively add directories'
cand -h 'Print help'
cand --help 'Print help'
cand -V 'Print version'
@ -105,6 +107,8 @@ set edit:completion:arg-completer[zoxide] = {|@words|
cand --version 'Print version'
}
&'zoxide;remove'= {
cand -r 'Recursively remove directories'
cand --recursive 'Recursively remove directories'
cand -h 'Print help'
cand --help 'Print help'
cand -V 'Print version'

View File

@ -33,6 +33,7 @@ complete -c zoxide -n "__fish_zoxide_needs_command" -f -a "init" -d 'Generate sh
complete -c zoxide -n "__fish_zoxide_needs_command" -f -a "query" -d 'Search for a directory in the database'
complete -c zoxide -n "__fish_zoxide_needs_command" -f -a "remove" -d 'Remove a directory from the database'
complete -c zoxide -n "__fish_zoxide_using_subcommand add" -s s -l score -d 'The rank to increment the entry if it exists or initialize it with if it doesn\'t' -r
complete -c zoxide -n "__fish_zoxide_using_subcommand add" -s r -l recursive -d 'Recursively add directories'
complete -c zoxide -n "__fish_zoxide_using_subcommand add" -s h -l help -d 'Print help'
complete -c zoxide -n "__fish_zoxide_using_subcommand add" -s V -l version -d 'Print version'
complete -c zoxide -n "__fish_zoxide_using_subcommand edit; and not __fish_seen_subcommand_from decrement delete increment reload" -s h -l help -d 'Print help'
@ -69,5 +70,6 @@ complete -c zoxide -n "__fish_zoxide_using_subcommand query" -s l -l list -d 'Li
complete -c zoxide -n "__fish_zoxide_using_subcommand query" -s s -l score -d 'Print score with results'
complete -c zoxide -n "__fish_zoxide_using_subcommand query" -s h -l help -d 'Print help'
complete -c zoxide -n "__fish_zoxide_using_subcommand query" -s V -l version -d 'Print version'
complete -c zoxide -n "__fish_zoxide_using_subcommand remove" -s r -l recursive -d 'Recursively remove directories'
complete -c zoxide -n "__fish_zoxide_using_subcommand remove" -s h -l help -d 'Print help'
complete -c zoxide -n "__fish_zoxide_using_subcommand remove" -s V -l version -d 'Print version'

View File

@ -10,6 +10,7 @@ module completions {
export extern "zoxide add" [
...paths: path
--score(-s): string # The rank to increment the entry if it exists or initialize it with if it doesn't
--recursive(-r) # Recursively add directories
--help(-h) # Print help
--version(-V) # Print version
]
@ -90,6 +91,7 @@ module completions {
# Remove a directory from the database
export extern "zoxide remove" [
...paths: path
--recursive(-r) # Recursively remove directories
--help(-h) # Print help
--version(-V) # Print version
]

View File

@ -15,6 +15,10 @@ const completion: Fig.Spec = {
isOptional: true,
},
},
{
name: ["-r", "--recursive"],
description: "Recursively add directories",
},
{
name: ["-h", "--help"],
description: "Print help",
@ -267,6 +271,10 @@ const completion: Fig.Spec = {
name: "remove",
description: "Remove a directory from the database",
options: [
{
name: ["-r", "--recursive"],
description: "Recursively remove directories",
},
{
name: ["-h", "--help"],
description: "Print help",

View File

@ -1,4 +1,4 @@
use std::path::Path;
use std::path::{Path, PathBuf};
use anyhow::{Result, bail};
@ -18,10 +18,17 @@ impl Run for Add {
let mut db = Database::open()?;
for path in &self.paths {
// Build a unified iterator of PathBufs, whether recursive or not.
let paths: Box<dyn Iterator<Item = PathBuf>> = if self.recursive {
Box::new(self.paths.iter().flat_map(|p| util::walk_dir(p)))
} else {
Box::new(self.paths.iter().cloned())
};
for path in paths {
let path =
if config::resolve_symlinks() { util::canonicalize } else { util::resolve_path }(
path,
&path,
)?;
let path = util::path_to_str(&path)?;

View File

@ -63,6 +63,10 @@ pub struct Add {
/// doesn't
#[clap(short, long)]
pub score: Option<f64>,
/// Recursively add directories
#[clap(short, long, default_value_t = false)]
pub recursive: bool,
}
/// Edit the database
@ -201,4 +205,8 @@ pub struct Query {
pub struct Remove {
#[clap(value_hint = ValueHint::DirPath)]
pub paths: Vec<String>,
/// Recursively remove directories
#[clap(short, long, default_value_t = false)]
pub recursive: bool,
}

View File

@ -17,7 +17,7 @@ impl Run for Edit {
match cmd {
EditCommand::Decrement { path } => db.add(path, -1.0, now),
EditCommand::Delete { path } => {
db.remove(path);
db.remove(path, false);
}
EditCommand::Increment { path } => db.add(path, 1.0, now),
EditCommand::Reload => {}

View File

@ -9,10 +9,10 @@ impl Run for Remove {
let mut db = Database::open()?;
for path in &self.paths {
if !db.remove(path) {
if !db.remove(path, self.recursive) {
let path_abs = util::resolve_path(path)?;
let path_abs = util::path_to_str(&path_abs)?;
if path_abs == path || !db.remove(path_abs) {
if path_abs == path || !db.remove(path_abs, self.recursive) {
bail!("path not found in database: {path}")
}
}

View File

@ -101,17 +101,35 @@ impl Database {
}
/// Removes the directory with `path` from the store. This does not preserve
/// ordering, but is O(1).
pub fn remove(&mut self, path: impl AsRef<str>) -> bool {
match self.dirs().iter().position(|dir| dir.path == path.as_ref()) {
Some(idx) => {
self.swap_remove(idx);
true
/// ordering, but is O(1). If recursive, this will remove all directories
/// starting with `path`. This is O(n)
pub fn remove(&mut self, path: impl AsRef<str>, recursive: bool) -> bool {
if recursive {
self.remove_recursive(path)
} else {
match self.dirs().iter().position(|dir| dir.path == path.as_ref()) {
Some(idx) => {
self.swap_remove(idx);
true
}
None => false,
}
None => false,
}
}
pub fn remove_recursive(&mut self, path: impl AsRef<str>) -> bool {
let mut removed = false;
self.with_dirs_mut(|dirs| {
dirs.retain(|dir| {
let keep = !dir.path.starts_with(path.as_ref());
removed |= !keep;
keep
});
});
self.with_dirty_mut(|dirty| *dirty = removed);
removed
}
pub fn swap_remove(&mut self, idx: usize) {
self.with_dirs_mut(|dirs| dirs.swap_remove(idx));
self.with_dirty_mut(|dirty| *dirty = true);
@ -273,14 +291,14 @@ mod tests {
{
let mut db = Database::open_dir(data_dir.path()).unwrap();
assert!(db.remove(path));
assert!(db.remove(path, false));
db.save().unwrap();
}
{
let mut db = Database::open_dir(data_dir.path()).unwrap();
assert!(db.dirs().is_empty());
assert!(!db.remove(path));
assert!(!db.remove(path, false));
db.save().unwrap();
}
}

View File

@ -9,6 +9,7 @@ use std::{env, mem};
#[cfg(windows)]
use anyhow::anyhow;
use anyhow::{Context, Result, bail};
use walkdir::WalkDir;
use crate::db::{Dir, Epoch};
use crate::error::SilentExit;
@ -382,3 +383,12 @@ pub fn to_lowercase(s: impl AsRef<str>) -> String {
let s = s.as_ref();
if s.is_ascii() { s.to_ascii_lowercase() } else { s.to_lowercase() }
}
/// Recursively walk a directory and yield all directories.
pub fn walk_dir(path: &Path) -> impl Iterator<Item = PathBuf> {
WalkDir::new(path)
.into_iter()
.filter_map(|p| p.ok())
.filter(|p| p.file_type().is_dir())
.map(|p| p.into_path())
}