Performance improvements for queries (#185)

This commit is contained in:
Ajeet D'Souza 2021-04-16 15:31:11 +05:30 committed by GitHub
parent 1828414f21
commit 5cb091d30a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 54 additions and 27 deletions

View File

@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased ## Unreleased
### Added
- Manpages for each subcommand.
### Fixed ### Fixed
- `cd -` on fish shells. - `cd -` on fish shells.

View File

@ -3,7 +3,7 @@ name = "zoxide"
version = "0.6.0" version = "0.6.0"
authors = ["Ajeet D'Souza <98ajeet@gmail.com>"] authors = ["Ajeet D'Souza <98ajeet@gmail.com>"]
edition = "2018" edition = "2018"
description = "A faster way to navigate your filesystem" description = "A smarter cd command"
repository = "https://github.com/ajeetdsouza/zoxide/" repository = "https://github.com/ajeetdsouza/zoxide/"
license = "MIT" license = "MIT"
keywords = ["cli"] keywords = ["cli"]

View File

@ -2,7 +2,7 @@
[![crates.io][crates.io-badge]][crates.io] [![crates.io][crates.io-badge]][crates.io]
> A faster way to navigate your filesystem > A smarter cd command
`zoxide` is a blazing fast replacement for your `cd` command, inspired by `zoxide` is a blazing fast replacement for your `cd` command, inspired by
[`z`][z] and [`z.lua`][z.lua]. It keeps track of the directories you use most [`z`][z] and [`z.lua`][z.lua]. It keeps track of the directories you use most

View File

@ -1,6 +1,6 @@
.TH "zoxide" "1" "2021-04-12" "zoxide" "zoxide" .TH "zoxide" "1" "2021-04-12" "zoxide" "zoxide"
.SH NAME .SH NAME
zoxide - a faster way to navigate your filesystem zoxide - a smarter cd command
.SH SYNOPSIS .SH SYNOPSIS
.B zoxide \fISUBCOMMAND [OPTIONS]\fR .B zoxide \fISUBCOMMAND [OPTIONS]\fR
.SH DESCRIPTION .SH DESCRIPTION

View File

@ -8,7 +8,7 @@ use crate::util;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use clap::Clap; use clap::Clap;
use std::io::{self, Write}; use std::io::{self, BufWriter, Write};
/// Search for a directory in the database /// Search for a directory in the database
#[derive(Clap, Debug)] #[derive(Clap, Debug)]
@ -52,6 +52,7 @@ impl Cmd for Query {
for dir in matches { for dir in matches {
writeln!(handle, "{}", dir.display_score(now)).handle_err("fzf")?; writeln!(handle, "{}", dir.display_score(now)).handle_err("fzf")?;
} }
let selection = fzf.wait_select()?; let selection = fzf.wait_select()?;
if self.score { if self.score {
print!("{}", selection); print!("{}", selection);
@ -62,8 +63,13 @@ impl Cmd for Query {
print!("{}", path) print!("{}", path)
} }
} else if self.list { } else if self.list {
// Rust does line-buffering by default, i.e. it flushes stdout
// after every newline. This is not ideal when printing a large
// number of lines, so we put stdout in a BufWriter.
let stdout = io::stdout(); let stdout = io::stdout();
let handle = &mut stdout.lock(); let stdout = stdout.lock();
let mut handle = BufWriter::new(stdout);
for dir in matches { for dir in matches {
if self.score { if self.score {
writeln!(handle, "{}", dir.display_score(now)) writeln!(handle, "{}", dir.display_score(now))
@ -72,6 +78,7 @@ impl Cmd for Query {
} }
.handle_err("stdout")?; .handle_err("stdout")?;
} }
handle.flush().handle_err("stdout")?;
} else { } else {
let dir = matches.next().context("no match found")?; let dir = matches.next().context("no match found")?;
if self.score { if self.score {

View File

@ -8,36 +8,25 @@ impl Query {
I: IntoIterator<Item = S>, I: IntoIterator<Item = S>,
S: AsRef<str>, S: AsRef<str>,
{ {
Query( Query(keywords.into_iter().map(|s: S| to_lowercase(s)).collect())
keywords
.into_iter()
.map(|s: S| s.as_ref().to_lowercase())
.collect(),
)
}
pub fn keywords(&self) -> &[String] {
&self.0
} }
pub fn matches<S: AsRef<str>>(&self, path: S) -> bool { pub fn matches<S: AsRef<str>>(&self, path: S) -> bool {
let path = path.as_ref().to_lowercase(); let keywords = &self.0;
let keywords = self.keywords(); let keywords_last = match keywords.last() {
Some(keyword) => keyword,
let get_filenames = || { None => return true,
let query_name = Path::new(keywords.last()?).file_name()?.to_str().unwrap();
let dir_name = Path::new(&path).file_name()?.to_str().unwrap();
Some((query_name, dir_name))
}; };
if let Some((query_name, dir_name)) = get_filenames() { let path = to_lowercase(path);
if !dir_name.contains(query_name) {
return false; let query_name = get_filename(keywords_last);
} let dir_name = get_filename(&path);
if !dir_name.contains(query_name) {
return false;
} }
let mut subpath = path.as_str(); let mut subpath = path.as_str();
for keyword in keywords.iter() { for keyword in keywords.iter() {
match subpath.find(keyword) { match subpath.find(keyword) {
Some(idx) => subpath = &subpath[idx + keyword.len()..], Some(idx) => subpath = &subpath[idx + keyword.len()..],
@ -49,6 +38,33 @@ impl Query {
} }
} }
fn get_filename(mut path: &str) -> &str {
if cfg!(windows) {
Path::new(path)
.file_name()
.unwrap_or_default()
.to_str()
.unwrap_or_default()
} else {
if path.ends_with('/') {
path = &path[..path.len() - 1];
}
match path.rfind('/') {
Some(idx) => &path[idx + 1..],
None => path,
}
}
}
fn to_lowercase<S: AsRef<str>>(s: S) -> String {
let s = s.as_ref();
if s.is_ascii() {
s.to_ascii_lowercase()
} else {
s.to_lowercase()
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::Query; use super::Query;