mirror of
https://github.com/Llewellynvdm/zoxide.git
synced 2024-11-25 22:17:33 +00:00
Improvements to query algorithm
This commit is contained in:
parent
1075ba5a50
commit
efe11ec924
@ -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
8
Cargo.lock
generated
@ -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",
|
||||
|
@ -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"]
|
||||
|
@ -1,6 +1,6 @@
|
||||
# `zoxide`
|
||||
|
||||
> A smarter cd command
|
||||
> A smarter cd command for your terminal
|
||||
|
||||
[![crates.io][crates.io-badge]][crates.io]
|
||||
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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);
|
||||
|
122
src/db/query.rs
122
src/db/query.rs
@ -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"));
|
||||
}
|
||||
}
|
||||
|
10
src/util.rs
10
src/util.rs
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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 }}
|
||||
|
Loading…
Reference in New Issue
Block a user