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
|
### Removed
|
||||||
|
|
||||||
- GitHub install script.
|
- GitHub install script.
|
||||||
|
- Release binaries built with `glibc`, use `musl` instead.
|
||||||
|
|
||||||
## [0.6.0] - 2021-04-09
|
## [0.6.0] - 2021-04-09
|
||||||
|
|
||||||
|
8
Cargo.lock
generated
8
Cargo.lock
generated
@ -519,9 +519,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.2.7"
|
version = "0.2.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "85dd92e586f7355c633911e11f77f3d12f04b1b1bd76a198bd34ae3af8341ef2"
|
checksum = "742739e41cd49414de871ea5e549afb7e2a3ac77b589bcbebe8c82fab37147fc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
]
|
]
|
||||||
@ -600,9 +600,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.71"
|
version = "1.0.72"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ad184cc9470f9117b2ac6817bfe297307418819ba40552f9b3846f05c33d5373"
|
checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -3,8 +3,8 @@ name = "zoxide"
|
|||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
authors = ["Ajeet D'Souza <98ajeet@gmail.com>"]
|
authors = ["Ajeet D'Souza <98ajeet@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "A smarter cd command"
|
description = "A smarter cd command for your terminal"
|
||||||
repository = "https://github.com/ajeetdsouza/zoxide/"
|
repository = "https://github.com/ajeetdsouza/zoxide"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
keywords = ["cli"]
|
keywords = ["cli"]
|
||||||
categories = ["command-line-utilities", "filesystem"]
|
categories = ["command-line-utilities", "filesystem"]
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# `zoxide`
|
# `zoxide`
|
||||||
|
|
||||||
> A smarter cd command
|
> A smarter cd command for your terminal
|
||||||
|
|
||||||
[![crates.io][crates.io-badge]][crates.io]
|
[![crates.io][crates.io-badge]][crates.io]
|
||||||
|
|
||||||
|
@ -14,14 +14,14 @@ impl Run for Import {
|
|||||||
let data_dir = config::zo_data_dir()?;
|
let data_dir = config::zo_data_dir()?;
|
||||||
|
|
||||||
let mut db = DatabaseFile::new(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() {
|
if !self.merge && !db.dirs.is_empty() {
|
||||||
bail!("current database is not empty, specify --merge to continue anyway");
|
bail!("current database is not empty, specify --merge to continue anyway");
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.from {
|
match self.from {
|
||||||
ImportFrom::Autojump => from_autojump(&mut db, &self.path),
|
ImportFrom::Autojump => from_autojump(db, &self.path),
|
||||||
ImportFrom::Z => from_z(&mut db, &self.path),
|
ImportFrom::Z => from_z(db, &self.path),
|
||||||
}
|
}
|
||||||
.context("import error")
|
.context("import error")
|
||||||
}
|
}
|
||||||
|
@ -107,7 +107,6 @@ impl<'a> Database<'a> {
|
|||||||
|
|
||||||
for idx in (0..self.dirs.len()).rev() {
|
for idx in (0..self.dirs.len()).rev() {
|
||||||
let dir = &mut self.dirs[idx];
|
let dir = &mut self.dirs[idx];
|
||||||
|
|
||||||
dir.rank *= factor;
|
dir.rank *= factor;
|
||||||
if dir.rank < 1.0 {
|
if dir.rank < 1.0 {
|
||||||
self.dirs.swap_remove(idx);
|
self.dirs.swap_remove(idx);
|
||||||
|
126
src/db/query.rs
126
src/db/query.rs
@ -1,4 +1,6 @@
|
|||||||
use std::path::Path;
|
use crate::util;
|
||||||
|
|
||||||
|
use std::path;
|
||||||
|
|
||||||
pub struct Query(Vec<String>);
|
pub struct Query(Vec<String>);
|
||||||
|
|
||||||
@ -8,28 +10,31 @@ impl Query {
|
|||||||
I: IntoIterator<Item = S>,
|
I: IntoIterator<Item = S>,
|
||||||
S: AsRef<str>,
|
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 {
|
pub fn matches<S: AsRef<str>>(&self, path: S) -> bool {
|
||||||
let keywords = &self.0;
|
let keywords = &self.0;
|
||||||
let keywords_last = match keywords.last() {
|
let (keywords_last, keywords) = match keywords.split_last() {
|
||||||
Some(keyword) => keyword,
|
Some(split) => split,
|
||||||
None => return true,
|
None => return true,
|
||||||
};
|
};
|
||||||
|
|
||||||
let path = to_lowercase(path);
|
let path = util::to_lowercase(path);
|
||||||
|
let mut subpath = path.as_str();
|
||||||
let query_name = get_filename(keywords_last);
|
match subpath.rfind(keywords_last) {
|
||||||
let dir_name = get_filename(&path);
|
Some(idx) => {
|
||||||
if !dir_name.contains(query_name) {
|
if subpath[idx + keywords_last.len()..].contains(path::is_separator) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
subpath = &subpath[..idx];
|
||||||
|
}
|
||||||
|
None => return false,
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut subpath = path.as_str();
|
for keyword in keywords.iter().rev() {
|
||||||
for keyword in keywords.iter() {
|
match subpath.rfind(keyword) {
|
||||||
match subpath.find(keyword) {
|
Some(idx) => subpath = &subpath[..idx],
|
||||||
Some(idx) => subpath = &subpath[idx + keyword.len()..],
|
|
||||||
None => return false,
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Query;
|
use super::Query;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn query_normalization() {
|
fn query() {
|
||||||
assert!(Query::new(&["fOo", "bAr"]).matches("/foo/bar"));
|
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]
|
for &(keywords, path, is_match) in CASES {
|
||||||
fn query_filename() {
|
assert_eq!(is_match, Query::new(keywords).matches(path))
|
||||||
assert!(Query::new(&["ba"]).matches("/foo/bar"));
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[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())
|
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 section = "# =============================================================================\n#" -%}
|
||||||
{%- let not_configured = "# -- not configured --" -%}
|
{%- let not_configured = "# -- not configured --" -%}
|
||||||
|
{%- let newline = "{{$(char newline)}}" -%}
|
||||||
|
|
||||||
{{ section }}
|
{{ section }}
|
||||||
# Utility functions for zoxide.
|
# Utility functions for zoxide.
|
||||||
@ -27,9 +28,7 @@ def __zoxide_hook [] {
|
|||||||
|
|
||||||
{%- when InitHook::Pwd %}
|
{%- when InitHook::Pwd %}
|
||||||
def __zoxide_hook [] {}
|
def __zoxide_hook [] {}
|
||||||
|
echo `zoxide: PWD hooks are not supported on Nushell.{{ newline }} Use 'zoxide init nushell --hook prompt' instead.{{ newline }}`
|
||||||
printf "zoxide: PWD hooks are not supported on Nushell.\n Use 'zoxide init nushell --hook prompt' instead.\n"
|
|
||||||
|
|
||||||
{%- endmatch %}
|
{%- endmatch %}
|
||||||
|
|
||||||
{{ section }}
|
{{ section }}
|
||||||
@ -39,37 +38,52 @@ printf "zoxide: PWD hooks are not supported on Nushell.\n Use 'zoxide ini
|
|||||||
|
|
||||||
# Jump to a directory using only keywords.
|
# Jump to a directory using only keywords.
|
||||||
def __zoxide_z [...rest:string] {
|
def __zoxide_z [...rest:string] {
|
||||||
if $(echo $rest | length) == 1 {
|
let args = $(echo $rest | skip 1);
|
||||||
cd ~
|
if $(shells | where active == $true | get name) != filesystem {
|
||||||
} {
|
if $(echo $args | length) > 1 {
|
||||||
let args = $(echo $rest | skip 1);
|
echo `zoxide: can only jump directories on filesystem{{ newline }}`
|
||||||
if $(echo $args | length) == 1 {
|
|
||||||
let arg0 = $(echo $args | first 1);
|
|
||||||
if $arg0 == '-' {
|
|
||||||
cd -
|
|
||||||
} {
|
|
||||||
if $(echo $arg0 | path exists) {
|
|
||||||
cd $arg0
|
|
||||||
} {
|
|
||||||
cd $(zoxide query --exclude $(pwd) -- $args | str trim)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} {
|
} {
|
||||||
cd $(zoxide query --exclude $(pwd) -- $args | str trim)
|
cd $(echo $args)
|
||||||
}
|
|
||||||
}
|
|
||||||
{%- if echo %}
|
{%- if echo %}
|
||||||
echo $(pwd)
|
pwd
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
}
|
||||||
|
} {
|
||||||
|
if $(echo $args | length) == 0 {
|
||||||
|
cd ~
|
||||||
|
} {
|
||||||
|
if $(echo $args | length) == 1 {
|
||||||
|
let arg0 = $(echo $args | first 1);
|
||||||
|
if $arg0 == '-' {
|
||||||
|
cd -
|
||||||
|
} {
|
||||||
|
if $(echo $arg0 | path exists) {
|
||||||
|
cd $arg0
|
||||||
|
} {
|
||||||
|
cd $(zoxide query --exclude $(pwd) -- $args | str trim)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} {
|
||||||
|
cd $(zoxide query --exclude $(pwd) -- $args | str trim)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{%- if echo %}
|
||||||
|
pwd
|
||||||
|
{%- endif %}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Jump to a directory using interactive search.
|
# Jump to a directory using interactive search.
|
||||||
def __zoxide_zi [...rest:string] {
|
def __zoxide_zi [...rest:string] {
|
||||||
let args = $(echo $rest | skip 1)
|
if $(shells | where active == $true | get name) != filesystem {
|
||||||
cd $(zoxide query -i -- $args | str trim)
|
echo `zoxide: can only jump directories on filesystem{{ newline }}`
|
||||||
|
} {
|
||||||
|
let args = $(echo $rest | skip 1)
|
||||||
|
cd $(zoxide query -i -- $args | str trim)
|
||||||
{%- if echo %}
|
{%- if echo %}
|
||||||
echo $(pwd)
|
pwd
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{{ section }}
|
{{ section }}
|
||||||
|
Loading…
Reference in New Issue
Block a user