Add option to list all query results

This commit is contained in:
Ajeet D'Souza 2020-05-30 01:06:03 +05:30
parent e8eb685e58
commit 19bac0b31a
7 changed files with 121 additions and 80 deletions

View File

@ -3,8 +3,8 @@ use crate::db::Rank;
use anyhow::{bail, Context, Result}; use anyhow::{bail, Context, Result};
use std::env; use std::env;
use std::fs;
use std::ffi::OsString; use std::ffi::OsString;
use std::fs;
use std::path::PathBuf; use std::path::PathBuf;
pub fn zo_data_dir() -> Result<PathBuf> { pub fn zo_data_dir() -> Result<PathBuf> {

View File

@ -1,4 +1,5 @@
use anyhow::{bail, Context, Result}; use anyhow::{bail, Context, Result};
use float_ord::FloatOrd;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use uuid::Uuid; use uuid::Uuid;
@ -149,6 +150,10 @@ impl Db {
Ok(()) Ok(())
} }
pub fn matches<'a>(&'a mut self, now: Epoch, keywords: &[String]) -> DbMatches<'a> {
DbMatches::new(self, now, keywords)
}
fn get_path<P: AsRef<Path>>(data_dir: P) -> PathBuf { fn get_path<P: AsRef<Path>>(data_dir: P) -> PathBuf {
data_dir.as_ref().join("db.zo") data_dir.as_ref().join("db.zo")
} }
@ -167,6 +172,49 @@ impl Drop for Db {
} }
} }
/// Streaming iterator for matching entries
pub struct DbMatches<'a> {
db: &'a mut Db,
idxs: std::iter::Rev<std::ops::Range<usize>>,
keywords: Vec<String>,
}
impl<'a> DbMatches<'a> {
pub fn new(db: &'a mut Db, now: Epoch, keywords: &[String]) -> DbMatches<'a> {
db.dirs
.sort_unstable_by_key(|dir| FloatOrd(dir.get_frecency(now)));
let idxs = (0..db.dirs.len()).rev();
let keywords = keywords
.iter()
.map(|keyword| keyword.to_lowercase())
.collect();
DbMatches { db, idxs, keywords }
}
pub fn next(&mut self) -> Option<&Dir> {
for idx in &mut self.idxs {
let dir = &self.db.dirs[idx];
if !dir.is_match(&self.keywords) {
continue;
}
if !dir.is_valid() {
self.db.dirs.swap_remove(idx);
self.db.modified = true;
continue;
}
let dir = &self.db.dirs[idx];
return Some(dir);
}
None
}
}
pub type Rank = f64; pub type Rank = f64;
pub type Epoch = i64; // use a signed integer so subtraction can be performed on it pub type Epoch = i64; // use a signed integer so subtraction can be performed on it

View File

@ -8,8 +8,9 @@ use structopt::StructOpt;
use std::env; use std::env;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
/// Add a new directory or increment its rank
#[derive(Debug, StructOpt)] #[derive(Debug, StructOpt)]
#[structopt(about = "Add a new directory or increment its rank")] #[structopt()]
pub struct Add { pub struct Add {
path: Option<PathBuf>, path: Option<PathBuf>,
} }

View File

@ -7,12 +7,14 @@ use structopt::StructOpt;
use std::fs; use std::fs;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
/// Import from z database
#[derive(Debug, StructOpt)] #[derive(Debug, StructOpt)]
#[structopt(about = "Import from z database")] #[structopt()]
pub struct Import { pub struct Import {
path: PathBuf, path: PathBuf,
#[structopt(long, help = "Merge entries into existing database")] /// Merge entries into existing database
#[structopt(long)]
merge: bool, merge: bool,
} }

View File

@ -6,30 +6,24 @@ use structopt::StructOpt;
use std::io::{self, Write}; use std::io::{self, Write};
/// Generates shell configuration
#[derive(Debug, StructOpt)] #[derive(Debug, StructOpt)]
#[structopt(about = "Generates shell configuration")] #[structopt()]
pub struct Init { pub struct Init {
#[structopt(possible_values = &Shell::variants(), case_insensitive = true)] #[structopt(possible_values = &Shell::variants(), case_insensitive = true)]
shell: Shell, shell: Shell,
#[structopt( /// Renames the 'z' command and corresponding aliases
long, #[structopt(long, alias = "z-cmd", default_value = "z")]
help = "Renames the 'z' command and corresponding aliases",
alias = "z-cmd",
default_value = "z"
)]
cmd: String, cmd: String,
#[structopt( /// Prevents zoxide from defining any commands other than 'z'
long, #[structopt(long, alias = "no-define-aliases")]
alias = "no-define-aliases",
help = "Prevents zoxide from defining any commands other than 'z'"
)]
no_aliases: bool, no_aliases: bool,
/// Chooses event on which an entry is added to the database
#[structopt( #[structopt(
long, long,
help = "Chooses event on which an entry is added to the database",
possible_values = &Hook::variants(), possible_values = &Hook::variants(),
default_value = "pwd", default_value = "pwd",
case_insensitive = true case_insensitive = true

View File

@ -2,100 +2,95 @@ use crate::fzf::Fzf;
use crate::util; use crate::util;
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use float_ord::FloatOrd;
use structopt::StructOpt; use structopt::StructOpt;
use std::io::{self, Write};
use std::path::Path; use std::path::Path;
/// Search for a directory
#[derive(Debug, StructOpt)] #[derive(Debug, StructOpt)]
#[structopt(about = "Search for a directory")] #[structopt()]
pub struct Query { pub struct Query {
keywords: Vec<String>, keywords: Vec<String>,
#[structopt(short, long, help = "Opens an interactive selection menu using fzf")]
/// Opens an interactive selection menu using fzf
#[structopt(short, long, conflicts_with = "list")]
interactive: bool, interactive: bool,
/// List all matching directories
#[structopt(short, long, conflicts_with = "interactive")]
list: bool,
} }
impl Query { impl Query {
pub fn run(&self) -> Result<()> { pub fn run(&self) -> Result<()> {
let path_opt = if self.interactive { if self.list {
query_interactive(&self.keywords)? return query_list(&self.keywords);
} else { }
query(&self.keywords)?
};
match path_opt { if self.interactive {
Some(path) => println!("{}", path), return query_interactive(&self.keywords);
None => bail!("no match found"), }
};
Ok(()) // if the input is already a valid path, simply return it
if let [path] = self.keywords.as_slice() {
if Path::new(path).is_dir() {
println!("{}", path);
return Ok(());
}
}
query(&self.keywords)
} }
} }
fn query(keywords: &[String]) -> Result<Option<String>> { fn query(keywords: &[String]) -> Result<()> {
// if the input is already a valid path, simply return it
if let [path] = keywords {
if Path::new(path).is_dir() {
return Ok(Some(path.to_string()));
}
}
let mut db = util::get_db()?; let mut db = util::get_db()?;
let now = util::get_current_time()?; let now = util::get_current_time()?;
let keywords = keywords let mut matches = db.matches(now, keywords);
.iter()
.map(|keyword| keyword.to_lowercase())
.collect::<Vec<_>>();
db.dirs match matches.next() {
.sort_unstable_by_key(|dir| FloatOrd(dir.get_frecency(now))); Some(dir) => println!("{}", dir.path),
None => bail!("no match found"),
for idx in (0..db.dirs.len()).rev() {
let dir = &db.dirs[idx];
if !dir.is_match(&keywords) {
continue;
}
if !dir.is_valid() {
db.dirs.swap_remove(idx);
db.modified = true;
continue;
}
let path = &dir.path;
return Ok(Some(path.to_string()));
} }
Ok(None) Ok(())
} }
fn query_interactive(keywords: &[String]) -> Result<Option<String>> { fn query_interactive(keywords: &[String]) -> Result<()> {
let keywords = keywords
.iter()
.map(|keyword| keyword.to_lowercase())
.collect::<Vec<_>>();
let mut db = util::get_db()?; let mut db = util::get_db()?;
let now = util::get_current_time()?; let now = util::get_current_time()?;
let mut fzf = Fzf::new()?; let mut fzf = Fzf::new()?;
for idx in (0..db.dirs.len()).rev() { let mut matches = db.matches(now, keywords);
let dir = &db.dirs[idx];
if !dir.is_match(&keywords) {
continue;
}
if !dir.is_valid() {
db.dirs.swap_remove(idx);
db.modified = true;
continue;
}
while let Some(dir) = matches.next() {
fzf.write_dir(dir, now); fzf.write_dir(dir, now);
} }
fzf.wait_selection() match fzf.wait_selection()? {
Some(path) => println!("{}", path),
None => bail!("no match found"),
};
Ok(())
}
fn query_list(keywords: &[String]) -> Result<()> {
let mut db = util::get_db()?;
let now = util::get_current_time()?;
let mut matches = db.matches(now, keywords);
let stdout = io::stdout();
let mut handle = stdout.lock();
while let Some(dir) = matches.next() {
let path = &dir.path;
writeln!(handle, "{}", path).unwrap();
}
Ok(())
} }

View File

@ -3,8 +3,9 @@ use crate::util::{canonicalize, get_db, path_to_str};
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use structopt::StructOpt; use structopt::StructOpt;
/// Remove a directory
#[derive(Debug, StructOpt)] #[derive(Debug, StructOpt)]
#[structopt(about = "Remove a directory")] #[structopt()]
pub struct Remove { pub struct Remove {
path: String, path: String,
} }