Interactive mode for zoxide remove

This commit is contained in:
Ajeet D'Souza 2020-04-06 08:41:23 +05:30
parent b21dbefa22
commit 56218f35d3
6 changed files with 96 additions and 24 deletions

View File

@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
### Added
- Interactive mode for removing entries (`zoxide remove -i`).
- Aliases for interactive `query` and `remove` (`zqi` and `zri` respectively)
### Changed
- `zoxide remove` now throws an error if there was no match in the database.
- Interactive mode in `zoxide` no longer throws an error if `fzf` exits gracefully.
## [0.3.1] - 2020-04-03
### Added

View File

@ -239,7 +239,13 @@ impl DB {
}
}
pub fn query_all<'a>(&'a mut self, keywords: &'a [String]) -> impl Iterator<Item = &'a Dir> {
pub fn query_many<'a>(&'a mut self, keywords: &'a [String]) -> impl Iterator<Item = &'a Dir> {
self.query_all()
.iter()
.filter(move |dir| dir.is_match(keywords))
}
pub fn query_all(&mut self) -> &[Dir] {
let orig_len = self.data.dirs.len();
self.data.dirs.retain(Dir::is_dir);
@ -247,28 +253,34 @@ impl DB {
self.modified = true;
}
self.data
.dirs
.iter()
.filter(move |dir| dir.is_match(keywords))
self.data.dirs.as_slice()
}
pub fn remove<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
let path_canonicalized;
let path_abs = match path.as_ref().canonicalize() {
Ok(path_abs) => {
path_canonicalized = path_abs;
&path_canonicalized
if let Ok(path_abs) = path.as_ref().canonicalize() {
self.remove_exact(path_abs)
.or_else(|_| self.remove_exact(path))
} else {
self.remove_exact(path)
}
}
Err(_) => path.as_ref(),
};
if let Some(idx) = self.data.dirs.iter().position(|dir| dir.path == path_abs) {
pub fn remove_exact<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
if let Some(idx) = self
.data
.dirs
.iter()
.position(|dir| dir.path == path.as_ref())
{
self.data.dirs.swap_remove(idx);
self.modified = true;
}
Ok(())
} else {
bail!(
"could not find path in database: {}",
path.as_ref().display()
)
}
}
fn get_path_tmp(&self) -> PathBuf {

View File

@ -219,9 +219,14 @@ fn fish_alias(z_cmd: &str) -> String {
format!(
r#"
abbr -a zi '{} -i'
abbr -a za 'zoxide add'
abbr -a zq 'zoxide query'
abbr -a zqi 'zoxide query -i'
abbr -a zr 'zoxide remove'
abbr -a zri 'zoxide remove -i'
"#,
z_cmd
)
@ -231,9 +236,14 @@ fn posix_alias(z_cmd: &str) -> String {
format!(
r#"
alias zi='{} -i'
alias za='zoxide add'
alias zq='zoxide query'
alias zqi='zoxide query -i'
alias zr='zoxide remove'
alias zri='zoxide remove -i'
"#,
z_cmd
)

View File

@ -70,7 +70,7 @@ impl Query {
.collect::<Vec<_>>();
let mut db = util::get_db()?;
let dirs = db.query_all(&keywords);
let dirs = db.query_many(&keywords);
util::fzf_helper(now, dirs)
}
}

View File

@ -8,11 +8,29 @@ use std::path::PathBuf;
#[derive(Debug, StructOpt)]
#[structopt(about = "Remove a directory")]
pub struct Remove {
path: PathBuf,
#[structopt(required_unless("interactive"))]
path: Option<PathBuf>,
#[structopt(short, long, help = "Opens an interactive selection menu using fzf")]
interactive: bool,
}
impl Remove {
pub fn run(&self) -> Result<()> {
util::get_db()?.remove(&self.path)
if self.interactive {
let mut db = util::get_db()?;
let dirs = db.query_all();
let now = util::get_current_time()?;
if let Some(path_bytes) = util::fzf_helper(now, dirs)? {
let path = util::bytes_to_path(&path_bytes)?;
db.remove_exact(path)?;
}
Ok(())
} else {
// structopt guarantees that unwrap is safe here
let path = self.path.as_ref().unwrap();
util::get_db()?.remove(path)
}
}
}

View File

@ -12,15 +12,35 @@ use std::process::{Command, Stdio};
use std::time::SystemTime;
#[cfg(unix)]
pub fn path_to_bytes<P: AsRef<Path>>(path: &P) -> Option<&[u8]> {
pub fn path_to_bytes<P: AsRef<Path>>(path: &P) -> Result<&[u8]> {
use std::os::unix::ffi::OsStrExt;
Some(path.as_ref().as_os_str().as_bytes())
Ok(path.as_ref().as_os_str().as_bytes())
}
#[cfg(not(unix))]
pub fn path_to_bytes<P: AsRef<Path>>(path: &P) -> Option<&[u8]> {
Some(path.as_ref().to_str()?.as_bytes())
pub fn path_to_bytes<P: AsRef<Path>>(path: &P) -> Result<&[u8]> {
match path.as_ref().to_str() {
Some(path_str) => Ok(path_str.as_bytes()),
None => bail!("invalid Unicode in path"),
}
}
#[cfg(unix)]
pub fn bytes_to_path(bytes: &[u8]) -> Result<&Path> {
use std::ffi::OsStr;
use std::os::unix::ffi::OsStrExt;
Ok(Path::new(OsStr::from_bytes(bytes)))
}
#[cfg(not(unix))]
pub fn bytes_to_path(bytes: &[u8]) -> Result<&Path> {
use std::str;
str::from_utf8(bytes)
.map(Path::new)
.context("invalid Unicode in path")
}
pub fn get_db() -> Result<DB> {
@ -75,7 +95,7 @@ where
for &(dir, frecency) in dir_frecencies.iter() {
// ensure that frecency fits in 4 characters
if let Some(path_bytes) = path_to_bytes(&dir.path) {
if let Ok(path_bytes) = path_to_bytes(&dir.path) {
(|| {
write!(fzf_stdin, "{:>4} ", frecency)?;
fzf_stdin.write_all(path_bytes)?;