mirror of
https://github.com/Llewellynvdm/zoxide.git
synced 2024-11-10 23:30:57 +00:00
Multi-select for zoxide remove -i (#192)
This commit is contained in:
parent
0eb4418fd6
commit
027ae1df47
17
CHANGELOG.md
17
CHANGELOG.md
@ -9,16 +9,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Added
|
||||
|
||||
- Manpages for each subcommand.
|
||||
- Manpages for all subcommands.
|
||||
- Default prompt for Nushell.
|
||||
|
||||
### Changed
|
||||
|
||||
- `zoxide remove -i` now accepts multiple selections.
|
||||
- `zoxide add` no longer accepts zero parameters.
|
||||
- `$_ZO_EXCLUDE_DIRS` now defaults to `"$HOME"`.
|
||||
|
||||
### Fixed
|
||||
|
||||
- `cd -` on fish shells.
|
||||
- `__zoxide_hook` no longer changes value of `$?` within `$PROMPT_COMMAND` on bash.
|
||||
|
||||
## [0.6.0] - 2021-04-09
|
||||
|
||||
@ -122,7 +125,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Fixed
|
||||
|
||||
- `fish` no longer `cd`s to the user's home when no match is found.
|
||||
- fish no longer `cd`s to the user's home when no match is found.
|
||||
|
||||
## [0.3.1] - 2020-04-03
|
||||
|
||||
@ -162,7 +165,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Fixed
|
||||
|
||||
- Incorrect exit codes in `z` command on `fish`.
|
||||
- Incorrect exit codes in `z` command on fish.
|
||||
|
||||
### Removed
|
||||
|
||||
@ -175,7 +178,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- `$_ZO_ECHO` to echo match before `cd`ing.
|
||||
- Minimal `ranger` plugin.
|
||||
- PWD hook to only update the database when the current directory is changed.
|
||||
- Support for the `bash` shell.
|
||||
- Support for bash.
|
||||
- `migrate` subcommand to allow users to migrate from `z`.
|
||||
|
||||
### Fixed
|
||||
@ -189,11 +192,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- `init` subcommand to remove dependency on shell plugin managers.
|
||||
- Support for `z -` command to go to previous directory.
|
||||
- `Cargo.lock` for more reproducible builds.
|
||||
- Support for the `fish` shell.
|
||||
- Support for the fish shell.
|
||||
|
||||
### Fixed
|
||||
|
||||
- `_zoxide_precmd` overriding other precmd hooks on `zsh`.
|
||||
- `_zoxide_precmd` overriding other precmd hooks on zsh.
|
||||
|
||||
## [0.1.1] - 2020-03-08
|
||||
|
||||
@ -215,7 +218,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Added
|
||||
|
||||
- GitHub Actions pipeline to build and upload releases.
|
||||
- Support for the `zsh` shell.
|
||||
- Support for zsh.
|
||||
|
||||
[0.6.0]: https://github.com/ajeetdsouza/zoxide/compare/v0.5.0...v0.6.0
|
||||
[0.5.0]: https://github.com/ajeetdsouza/zoxide/compare/v0.4.3...v0.5.0
|
||||
|
25
Cargo.lock
generated
25
Cargo.lock
generated
@ -86,11 +86,10 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "1.3.2"
|
||||
version = "1.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d175dfa69e619905c4c3cdb7c3c203fa3bdd5d51184e3afdb2742c0280493772"
|
||||
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"serde",
|
||||
]
|
||||
|
||||
@ -125,9 +124,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.3.4"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
|
||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
@ -282,9 +281,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "lexical-core"
|
||||
version = "0.7.5"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21f866863575d0e1d654fbeeabdc927292fdf862873dc3c96c6f753357e13374"
|
||||
checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"bitflags",
|
||||
@ -511,9 +510,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.5"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9"
|
||||
checksum = "8270314b5ccceb518e7e578952f0b72b88222d02e8f77f5ecf7abbb673539041"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
@ -554,9 +553,9 @@ checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
||||
|
||||
[[package]]
|
||||
name = "seq-macro"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5b3bd665f328d73d7079123bfbd14a6edd74187667b5c6f340adfc65ea9d25a"
|
||||
checksum = "5a9f47faea3cad316faa914d013d24f471cd90bfca1a0c70f05a3f42c6441e99"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
@ -592,9 +591,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.69"
|
||||
version = "1.0.70"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48fe99c6bd8b1cc636890bcc071842de909d902c81ac7dab53ba33c421ab8ffb"
|
||||
checksum = "b9505f307c872bab8eb46f77ae357c8eba1fdacead58ee5a850116b1d7f82883"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -17,7 +17,6 @@ clap = "3.0.0-beta.2"
|
||||
dirs-next = "2.0.0"
|
||||
dunce = "1.0.1"
|
||||
glob = "0.3.0"
|
||||
once_cell = "1.4.1"
|
||||
ordered-float = "2.0.0"
|
||||
serde = { version = "1.0.116", features = ["derive"] }
|
||||
tempfile = "3.1.0"
|
||||
@ -27,6 +26,7 @@ rand = "0.7.3"
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = "1.0.1"
|
||||
once_cell = "1.4.1"
|
||||
seq-macro = "0.2.1"
|
||||
|
||||
[features]
|
||||
|
@ -3,7 +3,7 @@ use crate::config;
|
||||
use crate::db::DatabaseFile;
|
||||
use crate::util;
|
||||
|
||||
use anyhow::Result;
|
||||
use anyhow::{bail, Result};
|
||||
use clap::Clap;
|
||||
|
||||
use std::path::PathBuf;
|
||||
@ -11,20 +11,15 @@ use std::path::PathBuf;
|
||||
/// Add a new directory or increment its rank
|
||||
#[derive(Clap, Debug)]
|
||||
pub struct Add {
|
||||
path: Option<PathBuf>,
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl Cmd for Add {
|
||||
fn run(&self) -> Result<()> {
|
||||
let path = match &self.path {
|
||||
Some(path) => {
|
||||
if config::zo_resolve_symlinks() {
|
||||
util::canonicalize(&path)
|
||||
let path = if config::zo_resolve_symlinks() {
|
||||
util::canonicalize(&self.path)
|
||||
} else {
|
||||
util::resolve_path(&path)
|
||||
}
|
||||
}
|
||||
None => util::current_dir(),
|
||||
util::resolve_path(&self.path)
|
||||
}?;
|
||||
|
||||
if config::zo_exclude_dirs()?
|
||||
@ -34,6 +29,9 @@ impl Cmd for Add {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if !path.is_dir() {
|
||||
bail!("not a directory: {}", path.display());
|
||||
}
|
||||
let path = util::path_to_str(&path)?;
|
||||
let now = util::current_time()?;
|
||||
|
||||
|
@ -30,7 +30,7 @@ impl Cmd for Import {
|
||||
let mut db = DatabaseFile::new(data_dir);
|
||||
let mut db = db.open()?;
|
||||
if !self.merge && !db.dirs.is_empty() {
|
||||
bail!("current database is not empty, specify --merge to continue anyway")
|
||||
bail!("current database is not empty, specify --merge to continue anyway");
|
||||
}
|
||||
|
||||
let resolve_symlinks = config::zo_resolve_symlinks();
|
||||
|
@ -6,13 +6,11 @@ use crate::shell::{self, Hook, Opts};
|
||||
use anyhow::{Context, Result};
|
||||
use askama::Template;
|
||||
use clap::{ArgEnum, Clap};
|
||||
use once_cell::sync::OnceCell;
|
||||
|
||||
use std::io::{self, Write};
|
||||
|
||||
/// Generate shell configuration
|
||||
#[derive(Clap, Debug)]
|
||||
#[clap(after_help(env_help()))]
|
||||
pub struct Init {
|
||||
#[clap(arg_enum)]
|
||||
shell: Shell,
|
||||
@ -59,7 +57,7 @@ impl Cmd for Init {
|
||||
Shell::Zsh => shell::Zsh(opts).render(),
|
||||
}
|
||||
.context("could not render template")?;
|
||||
writeln!(io::stdout(), "{}", source).handle_err("stdout")
|
||||
writeln!(io::stdout(), "{}", source).wrap_write("stdout")
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,26 +72,3 @@ enum Shell {
|
||||
Xonsh,
|
||||
Zsh,
|
||||
}
|
||||
|
||||
fn env_help() -> &'static str {
|
||||
static ENV_HELP: OnceCell<String> = OnceCell::new();
|
||||
ENV_HELP.get_or_init(|| {
|
||||
#[cfg(unix)]
|
||||
const PATH_SPLIT_SEPARATOR: u8 = b':';
|
||||
#[cfg(windows)]
|
||||
const PATH_SPLIT_SEPARATOR: u8 = 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)
|
||||
})
|
||||
}
|
||||
|
@ -13,6 +13,14 @@ pub use init::Init;
|
||||
pub use query::Query;
|
||||
pub use remove::Remove;
|
||||
|
||||
const ENV_HELP: &str = "ENVIRONMENT VARIABLES:
|
||||
_ZO_DATA_DIR Path for zoxide data files
|
||||
_ZO_ECHO Prints the matched directory before navigating to it when set to 1
|
||||
_ZO_EXCLUDE_DIRS List of directory globs to be excluded
|
||||
_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";
|
||||
|
||||
pub trait Cmd {
|
||||
fn run(&self) -> Result<()>;
|
||||
}
|
||||
@ -21,6 +29,8 @@ pub trait Cmd {
|
||||
#[clap(
|
||||
about,
|
||||
author,
|
||||
after_help = ENV_HELP,
|
||||
global_setting(AppSettings::ColoredHelp),
|
||||
global_setting(AppSettings::DisableHelpSubcommand),
|
||||
global_setting(AppSettings::GlobalVersion),
|
||||
global_setting(AppSettings::VersionlessSubcommands),
|
||||
|
@ -47,10 +47,9 @@ impl Cmd for Query {
|
||||
.filter(|dir| Some(dir.path.as_ref()) != self.exclude.as_deref());
|
||||
|
||||
if self.interactive {
|
||||
let mut fzf = Fzf::new()?;
|
||||
let handle = fzf.stdin();
|
||||
let mut fzf = Fzf::new(false)?;
|
||||
for dir in matches {
|
||||
writeln!(handle, "{}", dir.display_score(now)).handle_err("fzf")?;
|
||||
writeln!(fzf.stdin(), "{}", dir.display_score(now)).wrap_write("fzf")?;
|
||||
}
|
||||
|
||||
let selection = fzf.wait_select()?;
|
||||
@ -76,9 +75,9 @@ impl Cmd for Query {
|
||||
} else {
|
||||
writeln!(handle, "{}", dir.display())
|
||||
}
|
||||
.handle_err("stdout")?;
|
||||
.wrap_write("stdout")?;
|
||||
}
|
||||
handle.flush().handle_err("stdout")?;
|
||||
handle.flush().wrap_write("stdout")?;
|
||||
} else {
|
||||
let dir = matches.next().context("no match found")?;
|
||||
if self.score {
|
||||
@ -86,7 +85,7 @@ impl Cmd for Query {
|
||||
} else {
|
||||
writeln!(io::stdout(), "{}", dir.display())
|
||||
}
|
||||
.handle_err("stdout")?;
|
||||
.wrap_write("stdout")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -5,7 +5,7 @@ use crate::error::WriteErrorHandler;
|
||||
use crate::fzf::Fzf;
|
||||
use crate::util;
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
use anyhow::{bail, Result};
|
||||
use clap::Clap;
|
||||
|
||||
use std::io::Write;
|
||||
@ -30,31 +30,45 @@ impl Cmd for Remove {
|
||||
let mut db = db.open()?;
|
||||
|
||||
let selection;
|
||||
let path = match &self.interactive {
|
||||
match &self.interactive {
|
||||
Some(keywords) => {
|
||||
let query = Query::new(keywords);
|
||||
let now = util::current_time()?;
|
||||
|
||||
let mut fzf = Fzf::new()?;
|
||||
let handle = fzf.stdin();
|
||||
let resolve_symlinks = config::zo_resolve_symlinks();
|
||||
|
||||
let mut fzf = Fzf::new(true)?;
|
||||
for dir in db.iter_matches(&query, now, resolve_symlinks) {
|
||||
writeln!(handle, "{}", dir.display_score(now)).handle_err("fzf")?;
|
||||
writeln!(fzf.stdin(), "{}", dir.display_score(now)).wrap_write("fzf")?;
|
||||
}
|
||||
|
||||
selection = fzf.wait_select()?;
|
||||
selection
|
||||
.get(5..selection.len().saturating_sub(1))
|
||||
.context("fzf returned invalid output")?
|
||||
let paths = selection.lines().filter_map(|line| line.get(5..));
|
||||
let mut not_found = Vec::new();
|
||||
for path in paths {
|
||||
if !db.remove(&path) {
|
||||
not_found.push(path);
|
||||
}
|
||||
}
|
||||
None => self.path.as_ref().unwrap(),
|
||||
};
|
||||
|
||||
if !not_found.is_empty() {
|
||||
let mut err = "path not found in database:".to_string();
|
||||
for path in not_found {
|
||||
err.push_str("\n ");
|
||||
err.push_str(path.as_ref());
|
||||
}
|
||||
bail!(err);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// unwrap is safe here because path is required_unless_present = "interactive"
|
||||
let path = self.path.as_ref().unwrap();
|
||||
if !db.remove(path) {
|
||||
let path = util::resolve_path(&path)?;
|
||||
let path = util::path_to_str(&path)?;
|
||||
if !db.remove(path) {
|
||||
bail!("path not found in database: {}", &path)
|
||||
let path_abs = util::resolve_path(&path)?;
|
||||
let path_abs = util::path_to_str(&path_abs)?;
|
||||
if path_abs != path && !db.remove(path) {
|
||||
bail!("path not found in database:\n {}", &path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,11 +16,11 @@ impl Display for SilentExit {
|
||||
}
|
||||
|
||||
pub trait WriteErrorHandler {
|
||||
fn handle_err(self, device: &str) -> Result<()>;
|
||||
fn wrap_write(self, device: &str) -> Result<()>;
|
||||
}
|
||||
|
||||
impl WriteErrorHandler for io::Result<()> {
|
||||
fn handle_err(self, device: &str) -> Result<()> {
|
||||
fn wrap_write(self, device: &str) -> Result<()> {
|
||||
match self {
|
||||
Err(e) if e.kind() == io::ErrorKind::BrokenPipe => bail!(SilentExit { code: 0 }),
|
||||
result => result.with_context(|| format!("could not write to {}", device)),
|
||||
|
@ -10,13 +10,15 @@ pub struct Fzf {
|
||||
}
|
||||
|
||||
impl Fzf {
|
||||
pub fn new() -> Result<Self> {
|
||||
pub fn new(multiple: bool) -> Result<Self> {
|
||||
let mut command = Command::new("fzf");
|
||||
if multiple {
|
||||
command.arg("-m");
|
||||
}
|
||||
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);
|
||||
}
|
||||
@ -27,6 +29,7 @@ impl Fzf {
|
||||
}
|
||||
|
||||
pub fn stdin(&mut self) -> &mut ChildStdin {
|
||||
// unwrap is safe here because command.stdin() has been piped
|
||||
self.child.stdin.as_mut().unwrap()
|
||||
}
|
||||
|
||||
|
@ -147,9 +147,5 @@ pub fn resolve_path<P: AsRef<Path>>(path: &P) -> Result<PathBuf> {
|
||||
}
|
||||
}
|
||||
|
||||
let result = stack.iter().collect::<PathBuf>();
|
||||
if !result.is_dir() {
|
||||
bail!("could not resolve path: {}", result.display());
|
||||
}
|
||||
Ok(result)
|
||||
Ok(stack.iter().collect())
|
||||
}
|
||||
|
@ -32,9 +32,9 @@ if (not (and (builtin:has-env __zoxide_hooked) (builtin:eq (builtin:get-env __zo
|
||||
{%- when Hook::None %}
|
||||
{{ not_configured }}
|
||||
{%- when Hook::Prompt %}
|
||||
edit:before-readline = [$@edit:before-readline []{ zoxide add $pwd }]
|
||||
edit:before-readline = [$@edit:before-readline []{ zoxide add -- $pwd }]
|
||||
{%- when Hook::Pwd %}
|
||||
after-chdir = [$@after-chdir [_]{ zoxide add $pwd }]
|
||||
after-chdir = [$@after-chdir [_]{ zoxide add -- $pwd }]
|
||||
{%- endmatch %}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user