diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a21c7d..c617e8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/Cargo.lock b/Cargo.lock index 1504580..1fa0dc3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index 1d9dee5..92b272e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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"] diff --git a/README.md b/README.md index bb246bb..0c5140c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # `zoxide` -> A smarter cd command +> A smarter cd command for your terminal [![crates.io][crates.io-badge]][crates.io] diff --git a/src/cmd/import.rs b/src/cmd/import.rs index c1992ca..169c51c 100644 --- a/src/cmd/import.rs +++ b/src/cmd/import.rs @@ -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") } diff --git a/src/db/mod.rs b/src/db/mod.rs index d53a4f0..c16243f 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -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); diff --git a/src/db/query.rs b/src/db/query.rs index 7a61a65..145ba41 100644 --- a/src/db/query.rs +++ b/src/db/query.rs @@ -1,4 +1,6 @@ -use std::path::Path; +use crate::util; + +use std::path; pub struct Query(Vec); @@ -8,28 +10,31 @@ impl Query { I: IntoIterator, S: AsRef, { - Query(keywords.into_iter().map(to_lowercase).collect()) + Query(keywords.into_iter().map(util::to_lowercase).collect()) } pub fn matches>(&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) { - return false; + 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: 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")); - } - - #[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")); + for &(keywords, path, is_match) in CASES { + assert_eq!(is_match, Query::new(keywords).matches(path)) + } } } diff --git a/src/util.rs b/src/util.rs index 293b576..5089ae6 100644 --- a/src/util.rs +++ b/src/util.rs @@ -149,3 +149,13 @@ pub fn resolve_path>(path: &P) -> Result { Ok(stack.iter().collect()) } + +// Convert a string to lowercase, with a fast path for ASCII strings. +pub fn to_lowercase>(s: S) -> String { + let s = s.as_ref(); + if s.is_ascii() { + s.to_ascii_lowercase() + } else { + s.to_lowercase() + } +} diff --git a/templates/nushell.txt b/templates/nushell.txt index d34993c..301e6c7 100644 --- a/templates/nushell.txt +++ b/templates/nushell.txt @@ -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,37 +38,52 @@ 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 { - cd ~ - } { - let args = $(echo $rest | skip 1); - 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) - } - } + 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 $(zoxide query --exclude $(pwd) -- $args | str trim) - } - } + cd $(echo $args) {%- if echo %} - echo $(pwd) + pwd {%- 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. def __zoxide_zi [...rest:string] { - let args = $(echo $rest | skip 1) - cd $(zoxide query -i -- $args | str trim) + 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 }}