diff --git a/CHANGELOG.md b/CHANGELOG.md index 8187b2f..ca13c6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- Manpages for each subcommand. + ### Fixed - `cd -` on fish shells. diff --git a/Cargo.toml b/Cargo.toml index d5eaff7..b0d6333 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "zoxide" version = "0.6.0" authors = ["Ajeet D'Souza <98ajeet@gmail.com>"] edition = "2018" -description = "A faster way to navigate your filesystem" +description = "A smarter cd command" repository = "https://github.com/ajeetdsouza/zoxide/" license = "MIT" keywords = ["cli"] diff --git a/README.md b/README.md index 9209d96..9b1a518 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![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 [`z`][z] and [`z.lua`][z.lua]. It keeps track of the directories you use most diff --git a/man/zoxide.1 b/man/zoxide.1 index 84fa168..b047754 100644 --- a/man/zoxide.1 +++ b/man/zoxide.1 @@ -1,6 +1,6 @@ .TH "zoxide" "1" "2021-04-12" "zoxide" "zoxide" .SH NAME -zoxide - a faster way to navigate your filesystem +zoxide - a smarter cd command .SH SYNOPSIS .B zoxide \fISUBCOMMAND [OPTIONS]\fR .SH DESCRIPTION diff --git a/src/cmd/query.rs b/src/cmd/query.rs index 1c324d7..d3d40e7 100644 --- a/src/cmd/query.rs +++ b/src/cmd/query.rs @@ -8,7 +8,7 @@ use crate::util; use anyhow::{Context, Result}; use clap::Clap; -use std::io::{self, Write}; +use std::io::{self, BufWriter, Write}; /// Search for a directory in the database #[derive(Clap, Debug)] @@ -52,6 +52,7 @@ impl Cmd for Query { for dir in matches { writeln!(handle, "{}", dir.display_score(now)).handle_err("fzf")?; } + let selection = fzf.wait_select()?; if self.score { print!("{}", selection); @@ -62,8 +63,13 @@ impl Cmd for Query { print!("{}", path) } } 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 handle = &mut stdout.lock(); + let stdout = stdout.lock(); + let mut handle = BufWriter::new(stdout); + for dir in matches { if self.score { writeln!(handle, "{}", dir.display_score(now)) @@ -72,6 +78,7 @@ impl Cmd for Query { } .handle_err("stdout")?; } + handle.flush().handle_err("stdout")?; } else { let dir = matches.next().context("no match found")?; if self.score { diff --git a/src/db/query.rs b/src/db/query.rs index f145085..1f9721b 100644 --- a/src/db/query.rs +++ b/src/db/query.rs @@ -8,36 +8,25 @@ impl Query { I: IntoIterator, S: AsRef, { - Query( - keywords - .into_iter() - .map(|s: S| s.as_ref().to_lowercase()) - .collect(), - ) - } - - pub fn keywords(&self) -> &[String] { - &self.0 + Query(keywords.into_iter().map(|s: S| to_lowercase(s)).collect()) } pub fn matches>(&self, path: S) -> bool { - let path = path.as_ref().to_lowercase(); - let keywords = self.keywords(); - - let get_filenames = || { - 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)) + let keywords = &self.0; + let keywords_last = match keywords.last() { + Some(keyword) => keyword, + None => return true, }; - if let Some((query_name, dir_name)) = get_filenames() { - if !dir_name.contains(query_name) { - return false; - } + 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 mut subpath = path.as_str(); - for keyword in keywords.iter() { match subpath.find(keyword) { 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: 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;