Improvements to query algorithm

This commit is contained in:
Ajeet D'Souza 2021-05-06 04:31:57 +05:30
parent 1075ba5a50
commit efe11ec924
9 changed files with 104 additions and 118 deletions

View File

@ -37,6 +37,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Removed
- GitHub install script.
- Release binaries built with `glibc`, use `musl` instead.
## [0.6.0] - 2021-04-09

8
Cargo.lock generated
View File

@ -519,9 +519,9 @@ dependencies = [
[[package]]
name = "redox_syscall"
version = "0.2.7"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85dd92e586f7355c633911e11f77f3d12f04b1b1bd76a198bd34ae3af8341ef2"
checksum = "742739e41cd49414de871ea5e549afb7e2a3ac77b589bcbebe8c82fab37147fc"
dependencies = [
"bitflags",
]
@ -600,9 +600,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.71"
version = "1.0.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad184cc9470f9117b2ac6817bfe297307418819ba40552f9b3846f05c33d5373"
checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82"
dependencies = [
"proc-macro2",
"quote",

View File

@ -3,8 +3,8 @@ name = "zoxide"
version = "0.7.0"
authors = ["Ajeet D'Souza <98ajeet@gmail.com>"]
edition = "2018"
description = "A smarter cd command"
repository = "https://github.com/ajeetdsouza/zoxide/"
description = "A smarter cd command for your terminal"
repository = "https://github.com/ajeetdsouza/zoxide"
license = "MIT"
keywords = ["cli"]
categories = ["command-line-utilities", "filesystem"]

View File

@ -1,6 +1,6 @@
# `zoxide`
> A smarter cd command
> A smarter cd command for your terminal
[![crates.io][crates.io-badge]][crates.io]

View File

@ -14,14 +14,14 @@ impl Run for Import {
let data_dir = config::zo_data_dir()?;
let mut db = DatabaseFile::new(data_dir);
let mut db = db.open()?;
let db = &mut db.open()?;
if !self.merge && !db.dirs.is_empty() {
bail!("current database is not empty, specify --merge to continue anyway");
}
match self.from {
ImportFrom::Autojump => from_autojump(&mut db, &self.path),
ImportFrom::Z => from_z(&mut db, &self.path),
ImportFrom::Autojump => from_autojump(db, &self.path),
ImportFrom::Z => from_z(db, &self.path),
}
.context("import error")
}

View File

@ -107,7 +107,6 @@ impl<'a> Database<'a> {
for idx in (0..self.dirs.len()).rev() {
let dir = &mut self.dirs[idx];
dir.rank *= factor;
if dir.rank < 1.0 {
self.dirs.swap_remove(idx);

View File

@ -1,4 +1,6 @@
use std::path::Path;
use crate::util;
use std::path;
pub struct Query(Vec<String>);
@ -8,28 +10,31 @@ impl Query {
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
Query(keywords.into_iter().map(to_lowercase).collect())
Query(keywords.into_iter().map(util::to_lowercase).collect())
}
pub fn matches<S: AsRef<str>>(&self, path: S) -> bool {
let keywords = &self.0;
let keywords_last = match keywords.last() {
Some(keyword) => keyword,
let (keywords_last, keywords) = match keywords.split_last() {
Some(split) => split,
None => return true,
};
let path = to_lowercase(path);
let query_name = get_filename(keywords_last);
let dir_name = get_filename(&path);
if !dir_name.contains(query_name) {
let path = util::to_lowercase(path);
let mut subpath = path.as_str();
match subpath.rfind(keywords_last) {
Some(idx) => {
if subpath[idx + keywords_last.len()..].contains(path::is_separator) {
return false;
}
subpath = &subpath[..idx];
}
None => return false,
}
let mut subpath = path.as_str();
for keyword in keywords.iter() {
match subpath.find(keyword) {
Some(idx) => subpath = &subpath[idx + keyword.len()..],
for keyword in keywords.iter().rev() {
match subpath.rfind(keyword) {
Some(idx) => subpath = &subpath[..idx],
None => return false,
}
}
@ -38,79 +43,36 @@ 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)]
mod tests {
use super::Query;
#[test]
fn query_normalization() {
assert!(Query::new(&["fOo", "bAr"]).matches("/foo/bar"));
}
fn query() {
const CASES: &[(&[&str], &str, bool)] = &[
// Case normalization
(&["fOo", "bAr"], "/foo/bar", true),
// Last component
(&["ba"], "/foo/bar", true),
(&["fo"], "/foo/bar", false),
// Slash as suffix
(&["foo/"], "/foo", false),
(&["foo/"], "/foo/bar", true),
(&["foo/"], "/foo/bar/baz", false),
(&["foo", "/"], "/foo", false),
(&["foo", "/"], "/foo/bar", true),
(&["foo", "/"], "/foo/bar/baz", true),
// Split components
(&["/", "fo", "/", "ar"], "/foo/bar", true),
(&["oo/ba"], "/foo/bar", true),
// Overlap
(&["foo", "o", "bar"], "/foo/bar", false),
(&["/foo/", "/bar"], "/foo/bar", false),
(&["/foo/", "/bar"], "/foo/baz/bar", true),
];
#[test]
fn query_filename() {
assert!(Query::new(&["ba"]).matches("/foo/bar"));
for &(keywords, path, is_match) in CASES {
assert_eq!(is_match, Query::new(keywords).matches(path))
}
#[test]
fn query_not_filename() {
assert!(!Query::new(&["fo"]).matches("/foo/bar"));
}
#[test]
fn query_not_filename_slash() {
assert!(!Query::new(&["foo/"]).matches("/foo/bar"));
}
#[test]
fn query_path_separator() {
assert!(Query::new(&["/", "fo", "/", "ar"]).matches("/foo/bar"));
}
#[test]
fn query_path_separator_between() {
assert!(Query::new(&["oo/ba"]).matches("/foo/bar"));
}
#[test]
fn query_overlap_text() {
assert!(!Query::new(&["foo", "o", "bar"]).matches("/foo/bar"));
}
#[test]
fn query_overlap_slash() {
assert!(!Query::new(&["/foo/", "/bar"]).matches("/foo/bar"));
}
#[test]
fn query_consecutive_slash() {
assert!(Query::new(&["/foo/", "/baz"]).matches("/foo/bar/baz"));
}
}

View File

@ -149,3 +149,13 @@ pub fn resolve_path<P: AsRef<Path>>(path: &P) -> Result<PathBuf> {
Ok(stack.iter().collect())
}
// Convert a string to lowercase, with a fast path for ASCII strings.
pub 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()
}
}

View File

@ -1,5 +1,6 @@
{%- let section = "# =============================================================================\n#" -%}
{%- let not_configured = "# -- not configured --" -%}
{%- let newline = "{{$(char newline)}}" -%}
{{ section }}
# Utility functions for zoxide.
@ -27,9 +28,7 @@ def __zoxide_hook [] {
{%- when InitHook::Pwd %}
def __zoxide_hook [] {}
printf "zoxide: PWD hooks are not supported on Nushell.\n Use 'zoxide init nushell --hook prompt' instead.\n"
echo `zoxide: PWD hooks are not supported on Nushell.{{ newline }} Use 'zoxide init nushell --hook prompt' instead.{{ newline }}`
{%- endmatch %}
{{ section }}
@ -39,10 +38,20 @@ printf "zoxide: PWD hooks are not supported on Nushell.\n Use 'zoxide ini
# Jump to a directory using only keywords.
def __zoxide_z [...rest:string] {
if $(echo $rest | length) == 1 {
let args = $(echo $rest | skip 1);
if $(shells | where active == $true | get name) != filesystem {
if $(echo $args | length) > 1 {
echo `zoxide: can only jump directories on filesystem{{ newline }}`
} {
cd $(echo $args)
{%- if echo %}
pwd
{%- endif %}
}
} {
if $(echo $args | length) == 0 {
cd ~
} {
let args = $(echo $rest | skip 1);
if $(echo $args | length) == 1 {
let arg0 = $(echo $args | first 1);
if $arg0 == '-' {
@ -59,17 +68,22 @@ def __zoxide_z [...rest:string] {
}
}
{%- if echo %}
echo $(pwd)
pwd
{%- endif %}
}
}
# Jump to a directory using interactive search.
def __zoxide_zi [...rest:string] {
if $(shells | where active == $true | get name) != filesystem {
echo `zoxide: can only jump directories on filesystem{{ newline }}`
} {
let args = $(echo $rest | skip 1)
cd $(zoxide query -i -- $args | str trim)
{%- if echo %}
echo $(pwd)
pwd
{%- endif %}
}
}
{{ section }}