mirror of
https://github.com/Llewellynvdm/zoxide.git
synced 2024-11-24 21:57:32 +00:00
Added better output in query when already in the matched directory (#463)
This commit is contained in:
parent
65c5e86968
commit
2c2286cea2
4
build.rs
4
build.rs
@ -9,8 +9,8 @@ fn main() {
|
|||||||
};
|
};
|
||||||
println!("cargo:rustc-env=ZOXIDE_VERSION={version}");
|
println!("cargo:rustc-env=ZOXIDE_VERSION={version}");
|
||||||
|
|
||||||
// Since we are generating completions in the package directory, we need to set this so that
|
// Since we are generating completions in the package directory, we need to set
|
||||||
// Cargo doesn't rebuild every time.
|
// this so that Cargo doesn't rebuild every time.
|
||||||
println!("cargo:rerun-if-changed=build.rs");
|
println!("cargo:rerun-if-changed=build.rs");
|
||||||
println!("cargo:rerun-if-changed=src/");
|
println!("cargo:rerun-if-changed=src/");
|
||||||
println!("cargo:rerun-if-changed=templates/");
|
println!("cargo:rerun-if-changed=templates/");
|
||||||
|
2
contrib/completions/_zoxide
generated
2
contrib/completions/_zoxide
generated
@ -117,7 +117,7 @@ _arguments "${_arguments_options[@]}" \
|
|||||||
;;
|
;;
|
||||||
(query)
|
(query)
|
||||||
_arguments "${_arguments_options[@]}" \
|
_arguments "${_arguments_options[@]}" \
|
||||||
'--exclude=[Exclude a path from results]:path:_files -/' \
|
'--exclude=[Exclude the current directory]:path:_files -/' \
|
||||||
'--all[Show deleted directories]' \
|
'--all[Show deleted directories]' \
|
||||||
'(-l --list)-i[Use interactive selection]' \
|
'(-l --list)-i[Use interactive selection]' \
|
||||||
'(-l --list)--interactive[Use interactive selection]' \
|
'(-l --list)--interactive[Use interactive selection]' \
|
||||||
|
2
contrib/completions/_zoxide.ps1
generated
2
contrib/completions/_zoxide.ps1
generated
@ -99,7 +99,7 @@ Register-ArgumentCompleter -Native -CommandName 'zoxide' -ScriptBlock {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
'zoxide;query' {
|
'zoxide;query' {
|
||||||
[CompletionResult]::new('--exclude', 'exclude', [CompletionResultType]::ParameterName, 'Exclude a path from results')
|
[CompletionResult]::new('--exclude', 'exclude', [CompletionResultType]::ParameterName, 'Exclude the current directory')
|
||||||
[CompletionResult]::new('--all', 'all', [CompletionResultType]::ParameterName, 'Show deleted directories')
|
[CompletionResult]::new('--all', 'all', [CompletionResultType]::ParameterName, 'Show deleted directories')
|
||||||
[CompletionResult]::new('-i', 'i', [CompletionResultType]::ParameterName, 'Use interactive selection')
|
[CompletionResult]::new('-i', 'i', [CompletionResultType]::ParameterName, 'Use interactive selection')
|
||||||
[CompletionResult]::new('--interactive', 'interactive', [CompletionResultType]::ParameterName, 'Use interactive selection')
|
[CompletionResult]::new('--interactive', 'interactive', [CompletionResultType]::ParameterName, 'Use interactive selection')
|
||||||
|
2
contrib/completions/zoxide.elv
generated
2
contrib/completions/zoxide.elv
generated
@ -87,7 +87,7 @@ set edit:completion:arg-completer[zoxide] = {|@words|
|
|||||||
cand --version 'Print version information'
|
cand --version 'Print version information'
|
||||||
}
|
}
|
||||||
&'zoxide;query'= {
|
&'zoxide;query'= {
|
||||||
cand --exclude 'Exclude a path from results'
|
cand --exclude 'Exclude the current directory'
|
||||||
cand --all 'Show deleted directories'
|
cand --all 'Show deleted directories'
|
||||||
cand -i 'Use interactive selection'
|
cand -i 'Use interactive selection'
|
||||||
cand --interactive 'Use interactive selection'
|
cand --interactive 'Use interactive selection'
|
||||||
|
2
contrib/completions/zoxide.fish
generated
2
contrib/completions/zoxide.fish
generated
@ -31,7 +31,7 @@ complete -c zoxide -n "__fish_seen_subcommand_from init" -l hook -d 'Changes how
|
|||||||
complete -c zoxide -n "__fish_seen_subcommand_from init" -l no-cmd -d 'Prevents zoxide from defining the `z` and `zi` commands'
|
complete -c zoxide -n "__fish_seen_subcommand_from init" -l no-cmd -d 'Prevents zoxide from defining the `z` and `zi` commands'
|
||||||
complete -c zoxide -n "__fish_seen_subcommand_from init" -s h -l help -d 'Print help information'
|
complete -c zoxide -n "__fish_seen_subcommand_from init" -s h -l help -d 'Print help information'
|
||||||
complete -c zoxide -n "__fish_seen_subcommand_from init" -s V -l version -d 'Print version information'
|
complete -c zoxide -n "__fish_seen_subcommand_from init" -s V -l version -d 'Print version information'
|
||||||
complete -c zoxide -n "__fish_seen_subcommand_from query" -l exclude -d 'Exclude a path from results' -r -f -a "(__fish_complete_directories)"
|
complete -c zoxide -n "__fish_seen_subcommand_from query" -l exclude -d 'Exclude the current directory' -r -f -a "(__fish_complete_directories)"
|
||||||
complete -c zoxide -n "__fish_seen_subcommand_from query" -l all -d 'Show deleted directories'
|
complete -c zoxide -n "__fish_seen_subcommand_from query" -l all -d 'Show deleted directories'
|
||||||
complete -c zoxide -n "__fish_seen_subcommand_from query" -s i -l interactive -d 'Use interactive selection'
|
complete -c zoxide -n "__fish_seen_subcommand_from query" -s i -l interactive -d 'Use interactive selection'
|
||||||
complete -c zoxide -n "__fish_seen_subcommand_from query" -s l -l list -d 'List all matching directories'
|
complete -c zoxide -n "__fish_seen_subcommand_from query" -s l -l list -d 'List all matching directories'
|
||||||
|
2
contrib/completions/zoxide.ts
generated
2
contrib/completions/zoxide.ts
generated
@ -196,7 +196,7 @@ const completion: Fig.Spec = {
|
|||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
name: "--exclude",
|
name: "--exclude",
|
||||||
description: "Exclude a path from results",
|
description: "Exclude the current directory",
|
||||||
isRepeatable: true,
|
isRepeatable: true,
|
||||||
args: {
|
args: {
|
||||||
name: "exclude",
|
name: "exclude",
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
group_imports = "StdExternalCrate"
|
group_imports = "StdExternalCrate"
|
||||||
imports_granularity = "Module"
|
imports_granularity = "Module"
|
||||||
max_width = 120
|
|
||||||
newline_style = "Native"
|
newline_style = "Native"
|
||||||
use_field_init_shorthand = true
|
use_field_init_shorthand = true
|
||||||
use_small_heuristics = "Max"
|
use_small_heuristics = "Max"
|
||||||
|
@ -8,8 +8,8 @@ use crate::{config, util};
|
|||||||
|
|
||||||
impl Run for Add {
|
impl Run for Add {
|
||||||
fn run(&self) -> Result<()> {
|
fn run(&self) -> Result<()> {
|
||||||
// These characters can't be printed cleanly to a single line, so they can cause confusion
|
// These characters can't be printed cleanly to a single line, so they can cause
|
||||||
// when writing to stdout.
|
// confusion when writing to stdout.
|
||||||
const EXCLUDE_CHARS: &[char] = &['\n', '\r'];
|
const EXCLUDE_CHARS: &[char] = &['\n', '\r'];
|
||||||
|
|
||||||
let exclude_dirs = config::exclude_dirs()?;
|
let exclude_dirs = config::exclude_dirs()?;
|
||||||
@ -19,10 +19,14 @@ impl Run for Add {
|
|||||||
let mut db = Database::open()?;
|
let mut db = Database::open()?;
|
||||||
|
|
||||||
for path in &self.paths {
|
for path in &self.paths {
|
||||||
let path = if config::resolve_symlinks() { util::canonicalize } else { util::resolve_path }(path)?;
|
let path =
|
||||||
|
if config::resolve_symlinks() { util::canonicalize } else { util::resolve_path }(
|
||||||
|
path,
|
||||||
|
)?;
|
||||||
let path = util::path_to_str(&path)?;
|
let path = util::path_to_str(&path)?;
|
||||||
|
|
||||||
// Ignore path if it contains unsupported characters, or if it's in the exclude list.
|
// Ignore path if it contains unsupported characters, or if it's in the exclude
|
||||||
|
// list.
|
||||||
if path.contains(EXCLUDE_CHARS) || exclude_dirs.iter().any(|glob| glob.matches(path)) {
|
if path.contains(EXCLUDE_CHARS) || exclude_dirs.iter().any(|glob| glob.matches(path)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -137,7 +137,7 @@ pub struct Query {
|
|||||||
#[clap(long, short)]
|
#[clap(long, short)]
|
||||||
pub score: bool,
|
pub score: bool,
|
||||||
|
|
||||||
/// Exclude a path from results
|
/// Exclude the current directory
|
||||||
#[clap(long, value_hint = ValueHint::DirPath, value_name = "path")]
|
#[clap(long, value_hint = ValueHint::DirPath, value_name = "path")]
|
||||||
pub exclude: Option<String>,
|
pub exclude: Option<String>,
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,8 @@ impl Run for Edit {
|
|||||||
|
|
||||||
let stdout = &mut io::stdout().lock();
|
let stdout = &mut io::stdout().lock();
|
||||||
for dir in db.dirs().iter().rev() {
|
for dir in db.dirs().iter().rev() {
|
||||||
write!(stdout, "{}\0", dir.display().with_score(now).with_separator('\t')).pipe_exit("fzf")?;
|
write!(stdout, "{}\0", dir.display().with_score(now).with_separator('\t'))
|
||||||
|
.pipe_exit("fzf")?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,9 @@ use crate::db::Database;
|
|||||||
|
|
||||||
impl Run for Import {
|
impl Run for Import {
|
||||||
fn run(&self) -> Result<()> {
|
fn run(&self) -> Result<()> {
|
||||||
let buffer = fs::read_to_string(&self.path)
|
let buffer = fs::read_to_string(&self.path).with_context(|| {
|
||||||
.with_context(|| format!("could not open database for importing: {}", &self.path.display()))?;
|
format!("could not open database for importing: {}", &self.path.display())
|
||||||
|
})?;
|
||||||
|
|
||||||
let mut db = Database::open()?;
|
let mut db = Database::open()?;
|
||||||
if !self.merge && !db.dirs().is_empty() {
|
if !self.merge && !db.dirs().is_empty() {
|
||||||
@ -34,8 +35,9 @@ fn import_autojump(db: &mut Database, buffer: &str) -> Result<()> {
|
|||||||
|
|
||||||
let rank = split.next().with_context(|| format!("invalid entry: {line}"))?;
|
let rank = split.next().with_context(|| format!("invalid entry: {line}"))?;
|
||||||
let mut rank = rank.parse::<f64>().with_context(|| format!("invalid rank: {rank}"))?;
|
let mut rank = rank.parse::<f64>().with_context(|| format!("invalid rank: {rank}"))?;
|
||||||
// Normalize the rank using a sigmoid function. Don't import actual ranks from autojump,
|
// Normalize the rank using a sigmoid function. Don't import actual ranks from
|
||||||
// since its scoring algorithm is very different and might take a while to get normalized.
|
// autojump, since its scoring algorithm is very different and might
|
||||||
|
// take a while to get normalized.
|
||||||
rank = sigmoid(rank);
|
rank = sigmoid(rank);
|
||||||
|
|
||||||
let path = split.next().with_context(|| format!("invalid entry: {line}"))?;
|
let path = split.next().with_context(|| format!("invalid entry: {line}"))?;
|
||||||
@ -57,7 +59,8 @@ fn import_z(db: &mut Database, buffer: &str) -> Result<()> {
|
|||||||
let mut split = line.rsplitn(3, '|');
|
let mut split = line.rsplitn(3, '|');
|
||||||
|
|
||||||
let last_accessed = split.next().with_context(|| format!("invalid entry: {line}"))?;
|
let last_accessed = split.next().with_context(|| format!("invalid entry: {line}"))?;
|
||||||
let last_accessed = last_accessed.parse().with_context(|| format!("invalid epoch: {last_accessed}"))?;
|
let last_accessed =
|
||||||
|
last_accessed.parse().with_context(|| format!("invalid epoch: {last_accessed}"))?;
|
||||||
|
|
||||||
let rank = split.next().with_context(|| format!("invalid entry: {line}"))?;
|
let rank = split.next().with_context(|| format!("invalid entry: {line}"))?;
|
||||||
let rank = rank.parse().with_context(|| format!("invalid rank: {rank}"))?;
|
let rank = rank.parse().with_context(|| format!("invalid rank: {rank}"))?;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
|
|
||||||
use crate::cmd::{Query, Run};
|
use crate::cmd::{Query, Run};
|
||||||
use crate::config;
|
use crate::config;
|
||||||
@ -47,7 +47,13 @@ impl Query {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let handle = &mut io::stdout();
|
let handle = &mut io::stdout();
|
||||||
let dir = stream.next().context("no match found")?;
|
let Some(dir) = stream.next() else {
|
||||||
|
bail!(if stream.did_exclude() {
|
||||||
|
"you are already in the only match"
|
||||||
|
} else {
|
||||||
|
"no match found"
|
||||||
|
});
|
||||||
|
};
|
||||||
let dir = if self.score { dir.display().with_score(now) } else { dir.display() };
|
let dir = if self.score { dir.display().with_score(now) } else { dir.display() };
|
||||||
writeln!(handle, "{dir}").pipe_exit("stdout")?;
|
writeln!(handle, "{dir}").pipe_exit("stdout")?;
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,8 @@ pub fn exclude_dirs() -> Result<Vec<Pattern>> {
|
|||||||
Some(paths) => env::split_paths(&paths)
|
Some(paths) => env::split_paths(&paths)
|
||||||
.map(|path| {
|
.map(|path| {
|
||||||
let pattern = path.to_str().context("invalid unicode in _ZO_EXCLUDE_DIRS")?;
|
let pattern = path.to_str().context("invalid unicode in _ZO_EXCLUDE_DIRS")?;
|
||||||
Pattern::new(pattern).with_context(|| format!("invalid glob in _ZO_EXCLUDE_DIRS: {pattern}"))
|
Pattern::new(pattern)
|
||||||
|
.with_context(|| format!("invalid glob in _ZO_EXCLUDE_DIRS: {pattern}"))
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
None => {
|
None => {
|
||||||
@ -47,8 +48,9 @@ pub fn fzf_opts() -> Option<OsString> {
|
|||||||
pub fn maxage() -> Result<Rank> {
|
pub fn maxage() -> Result<Rank> {
|
||||||
env::var_os("_ZO_MAXAGE").map_or(Ok(10_000.0), |maxage| {
|
env::var_os("_ZO_MAXAGE").map_or(Ok(10_000.0), |maxage| {
|
||||||
let maxage = maxage.to_str().context("invalid unicode in _ZO_MAXAGE")?;
|
let maxage = maxage.to_str().context("invalid unicode in _ZO_MAXAGE")?;
|
||||||
let maxage =
|
let maxage = maxage
|
||||||
maxage.parse::<u32>().with_context(|| format!("unable to parse _ZO_MAXAGE as integer: {maxage}"))?;
|
.parse::<u32>()
|
||||||
|
.with_context(|| format!("unable to parse _ZO_MAXAGE as integer: {maxage}"))?;
|
||||||
Ok(maxage as Rank)
|
Ok(maxage as Rank)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
use std::{
|
use std::borrow::Cow;
|
||||||
borrow::Cow,
|
use std::fmt::{self, Display, Formatter};
|
||||||
fmt::{self, Display, Formatter},
|
|
||||||
};
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
@ -37,13 +37,16 @@ impl Database {
|
|||||||
match fs::read(&path) {
|
match fs::read(&path) {
|
||||||
Ok(bytes) => Self::try_new(path, bytes, |bytes| Self::deserialize(bytes), false),
|
Ok(bytes) => Self::try_new(path, bytes, |bytes| Self::deserialize(bytes), false),
|
||||||
Err(e) if e.kind() == io::ErrorKind::NotFound => {
|
Err(e) if e.kind() == io::ErrorKind::NotFound => {
|
||||||
// Create data directory, but don't create any file yet. The file will be created
|
// Create data directory, but don't create any file yet. The file will be
|
||||||
// later by [`Database::save`] if any data is modified.
|
// created later by [`Database::save`] if any data is modified.
|
||||||
fs::create_dir_all(data_dir)
|
fs::create_dir_all(data_dir).with_context(|| {
|
||||||
.with_context(|| format!("unable to create data directory: {}", data_dir.display()))?;
|
format!("unable to create data directory: {}", data_dir.display())
|
||||||
|
})?;
|
||||||
Ok(Self::new(path, Vec::new(), |_| Vec::new(), false))
|
Ok(Self::new(path, Vec::new(), |_| Vec::new(), false))
|
||||||
}
|
}
|
||||||
Err(e) => Err(e).with_context(|| format!("could not read from database: {}", path.display())),
|
Err(e) => {
|
||||||
|
Err(e).with_context(|| format!("could not read from database: {}", path.display()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,7 +67,9 @@ impl Database {
|
|||||||
pub fn add(&mut self, path: impl AsRef<str> + Into<String>, by: Rank, now: Epoch) {
|
pub fn add(&mut self, path: impl AsRef<str> + Into<String>, by: Rank, now: Epoch) {
|
||||||
self.with_dirs_mut(|dirs| match dirs.iter_mut().find(|dir| dir.path == path.as_ref()) {
|
self.with_dirs_mut(|dirs| match dirs.iter_mut().find(|dir| dir.path == path.as_ref()) {
|
||||||
Some(dir) => dir.rank = (dir.rank + by).max(0.0),
|
Some(dir) => dir.rank = (dir.rank + by).max(0.0),
|
||||||
None => dirs.push(Dir { path: path.into().into(), rank: by.max(0.0), last_accessed: now }),
|
None => {
|
||||||
|
dirs.push(Dir { path: path.into().into(), rank: by.max(0.0), last_accessed: now })
|
||||||
|
}
|
||||||
});
|
});
|
||||||
self.with_dirty_mut(|dirty| *dirty = true);
|
self.with_dirty_mut(|dirty| *dirty = true);
|
||||||
}
|
}
|
||||||
@ -73,7 +78,9 @@ impl Database {
|
|||||||
/// directory is always in the database, it is expected that the user either
|
/// directory is always in the database, it is expected that the user either
|
||||||
/// does a check before calling this, or calls `dedup()` afterward.
|
/// does a check before calling this, or calls `dedup()` afterward.
|
||||||
pub fn add_unchecked(&mut self, path: impl AsRef<str> + Into<String>, rank: Rank, now: Epoch) {
|
pub fn add_unchecked(&mut self, path: impl AsRef<str> + Into<String>, rank: Rank, now: Epoch) {
|
||||||
self.with_dirs_mut(|dirs| dirs.push(Dir { path: path.into().into(), rank, last_accessed: now }));
|
self.with_dirs_mut(|dirs| {
|
||||||
|
dirs.push(Dir { path: path.into().into(), rank, last_accessed: now })
|
||||||
|
});
|
||||||
self.with_dirty_mut(|dirty| *dirty = true);
|
self.with_dirty_mut(|dirty| *dirty = true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,7 +92,9 @@ impl Database {
|
|||||||
dir.rank = (dir.rank + by).max(0.0);
|
dir.rank = (dir.rank + by).max(0.0);
|
||||||
dir.last_accessed = now;
|
dir.last_accessed = now;
|
||||||
}
|
}
|
||||||
None => dirs.push(Dir { path: path.into().into(), rank: by.max(0.0), last_accessed: now }),
|
None => {
|
||||||
|
dirs.push(Dir { path: path.into().into(), rank: by.max(0.0), last_accessed: now })
|
||||||
|
}
|
||||||
});
|
});
|
||||||
self.with_dirty_mut(|dirty| *dirty = true);
|
self.with_dirty_mut(|dirty| *dirty = true);
|
||||||
}
|
}
|
||||||
@ -166,7 +175,9 @@ impl Database {
|
|||||||
|
|
||||||
pub fn sort_by_score(&mut self, now: Epoch) {
|
pub fn sort_by_score(&mut self, now: Epoch) {
|
||||||
self.with_dirs_mut(|dirs| {
|
self.with_dirs_mut(|dirs| {
|
||||||
dirs.sort_unstable_by(|dir1: &Dir, dir2: &Dir| dir1.score(now).total_cmp(&dir2.score(now)))
|
dirs.sort_unstable_by(|dir1: &Dir, dir2: &Dir| {
|
||||||
|
dir1.score(now).total_cmp(&dir2.score(now))
|
||||||
|
})
|
||||||
});
|
});
|
||||||
self.with_dirty_mut(|dirty| *dirty = true);
|
self.with_dirty_mut(|dirty| *dirty = true);
|
||||||
}
|
}
|
||||||
@ -182,7 +193,8 @@ impl Database {
|
|||||||
fn serialize(dirs: &[Dir<'_>]) -> Result<Vec<u8>> {
|
fn serialize(dirs: &[Dir<'_>]) -> Result<Vec<u8>> {
|
||||||
(|| -> bincode::Result<_> {
|
(|| -> bincode::Result<_> {
|
||||||
// Preallocate buffer with combined size of sections.
|
// Preallocate buffer with combined size of sections.
|
||||||
let buffer_size = bincode::serialized_size(&Self::VERSION)? + bincode::serialized_size(&dirs)?;
|
let buffer_size =
|
||||||
|
bincode::serialized_size(&Self::VERSION)? + bincode::serialized_size(&dirs)?;
|
||||||
let mut buffer = Vec::with_capacity(buffer_size as usize);
|
let mut buffer = Vec::with_capacity(buffer_size as usize);
|
||||||
|
|
||||||
// Serialize sections into buffer.
|
// Serialize sections into buffer.
|
||||||
@ -195,8 +207,8 @@ impl Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn deserialize(bytes: &[u8]) -> Result<Vec<Dir>> {
|
fn deserialize(bytes: &[u8]) -> Result<Vec<Dir>> {
|
||||||
// Assume a maximum size for the database. This prevents bincode from throwing strange
|
// Assume a maximum size for the database. This prevents bincode from throwing
|
||||||
// errors when it encounters invalid data.
|
// strange errors when it encounters invalid data.
|
||||||
const MAX_SIZE: u64 = 32 << 20; // 32 MiB
|
const MAX_SIZE: u64 = 32 << 20; // 32 MiB
|
||||||
let deserializer = &mut bincode::options().with_fixint_encoding().with_limit(MAX_SIZE);
|
let deserializer = &mut bincode::options().with_fixint_encoding().with_limit(MAX_SIZE);
|
||||||
|
|
||||||
@ -210,7 +222,9 @@ impl Database {
|
|||||||
// Deserialize sections.
|
// Deserialize sections.
|
||||||
let version = deserializer.deserialize(bytes_version)?;
|
let version = deserializer.deserialize(bytes_version)?;
|
||||||
let dirs = match version {
|
let dirs = match version {
|
||||||
Self::VERSION => deserializer.deserialize(bytes_dirs).context("could not deserialize database")?,
|
Self::VERSION => {
|
||||||
|
deserializer.deserialize(bytes_dirs).context("could not deserialize database")?
|
||||||
|
}
|
||||||
version => {
|
version => {
|
||||||
bail!("unsupported version (got {version}, supports {})", Self::VERSION)
|
bail!("unsupported version (got {version}, supports {})", Self::VERSION)
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,12 @@ use crate::db::{Database, Dir, Epoch};
|
|||||||
use crate::util::{self, MONTH};
|
use crate::util::{self, MONTH};
|
||||||
|
|
||||||
pub struct Stream<'a> {
|
pub struct Stream<'a> {
|
||||||
|
// State
|
||||||
db: &'a mut Database,
|
db: &'a mut Database,
|
||||||
idxs: Rev<Range<usize>>,
|
idxs: Rev<Range<usize>>,
|
||||||
|
did_exclude: bool,
|
||||||
|
|
||||||
|
// Configuration
|
||||||
keywords: Vec<String>,
|
keywords: Vec<String>,
|
||||||
check_exists: bool,
|
check_exists: bool,
|
||||||
expire_below: Epoch,
|
expire_below: Epoch,
|
||||||
@ -27,6 +31,7 @@ impl<'a> Stream<'a> {
|
|||||||
Stream {
|
Stream {
|
||||||
db,
|
db,
|
||||||
idxs,
|
idxs,
|
||||||
|
did_exclude: false,
|
||||||
keywords: Vec::new(),
|
keywords: Vec::new(),
|
||||||
check_exists: false,
|
check_exists: false,
|
||||||
expire_below,
|
expire_below,
|
||||||
@ -67,6 +72,7 @@ impl<'a> Stream<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if Some(dir.path.as_ref()) == self.exclude_path.as_deref() {
|
if Some(dir.path.as_ref()) == self.exclude_path.as_deref() {
|
||||||
|
self.did_exclude = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,6 +83,10 @@ impl<'a> Stream<'a> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn did_exclude(&self) -> bool {
|
||||||
|
self.did_exclude
|
||||||
|
}
|
||||||
|
|
||||||
fn matches_exists(&self, path: &str) -> bool {
|
fn matches_exists(&self, path: &str) -> bool {
|
||||||
if !self.check_exists {
|
if !self.check_exists {
|
||||||
return true;
|
return true;
|
||||||
|
33
src/shell.rs
33
src/shell.rs
@ -98,14 +98,21 @@ mod tests {
|
|||||||
let opts = Opts { cmd, hook, echo, resolve_symlinks };
|
let opts = Opts { cmd, hook, echo, resolve_symlinks };
|
||||||
let mut source = String::new();
|
let mut source = String::new();
|
||||||
|
|
||||||
// Filter out lines using edit:*, since those functions are only available in the
|
// Filter out lines using edit:*, since those functions are only available in
|
||||||
// interactive editor.
|
// the interactive editor.
|
||||||
for line in Elvish(&opts).render().unwrap().split('\n').filter(|line| !line.contains("edit:")) {
|
for line in
|
||||||
|
Elvish(&opts).render().unwrap().split('\n').filter(|line| !line.contains("edit:"))
|
||||||
|
{
|
||||||
source.push_str(line);
|
source.push_str(line);
|
||||||
source.push('\n');
|
source.push('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
Command::new("elvish").args(["-c", &source, "-norc"]).assert().success().stdout("").stderr("");
|
Command::new("elvish")
|
||||||
|
.args(["-c", &source, "-norc"])
|
||||||
|
.assert()
|
||||||
|
.success()
|
||||||
|
.stdout("")
|
||||||
|
.stderr("");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[apply(opts)]
|
#[apply(opts)]
|
||||||
@ -151,8 +158,12 @@ mod tests {
|
|||||||
let tempdir = tempfile::tempdir().unwrap();
|
let tempdir = tempfile::tempdir().unwrap();
|
||||||
let tempdir = tempdir.path();
|
let tempdir = tempdir.path();
|
||||||
|
|
||||||
let assert =
|
let assert = Command::new("nu")
|
||||||
Command::new("nu").env("HOME", tempdir).args(["--commands", &source]).assert().success().stderr("");
|
.env("HOME", tempdir)
|
||||||
|
.args(["--commands", &source])
|
||||||
|
.assert()
|
||||||
|
.success()
|
||||||
|
.stderr("");
|
||||||
|
|
||||||
if opts.hook != InitHook::Pwd {
|
if opts.hook != InitHook::Pwd {
|
||||||
assert.stdout("");
|
assert.stdout("");
|
||||||
@ -179,7 +190,8 @@ mod tests {
|
|||||||
let opts = Opts { cmd, hook, echo, resolve_symlinks };
|
let opts = Opts { cmd, hook, echo, resolve_symlinks };
|
||||||
let source = Posix(&opts).render().unwrap();
|
let source = Posix(&opts).render().unwrap();
|
||||||
|
|
||||||
let assert = Command::new("dash").args(["-e", "-u", "-c", &source]).assert().success().stderr("");
|
let assert =
|
||||||
|
Command::new("dash").args(["-e", "-u", "-c", &source]).assert().success().stderr("");
|
||||||
if opts.hook != InitHook::Pwd {
|
if opts.hook != InitHook::Pwd {
|
||||||
assert.stdout("");
|
assert.stdout("");
|
||||||
}
|
}
|
||||||
@ -234,7 +246,12 @@ mod tests {
|
|||||||
let mut source = Xonsh(&opts).render().unwrap();
|
let mut source = Xonsh(&opts).render().unwrap();
|
||||||
source.push('\n');
|
source.push('\n');
|
||||||
|
|
||||||
Command::new("black").args(["--check", "--diff", "-"]).write_stdin(source).assert().success().stdout("");
|
Command::new("black")
|
||||||
|
.args(["--check", "--diff", "-"])
|
||||||
|
.write_stdin(source)
|
||||||
|
.assert()
|
||||||
|
.success()
|
||||||
|
.stdout("");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[apply(opts)]
|
#[apply(opts)]
|
||||||
|
40
src/util.rs
40
src/util.rs
@ -159,7 +159,9 @@ pub fn write(path: impl AsRef<Path>, contents: impl AsRef<[u8]>) -> Result<()> {
|
|||||||
let result = (|| {
|
let result = (|| {
|
||||||
// Write to the tmpfile.
|
// Write to the tmpfile.
|
||||||
let _ = tmp_file.set_len(contents.len() as u64);
|
let _ = tmp_file.set_len(contents.len() as u64);
|
||||||
tmp_file.write_all(contents).with_context(|| format!("could not write to file: {}", tmp_path.display()))?;
|
tmp_file
|
||||||
|
.write_all(contents)
|
||||||
|
.with_context(|| format!("could not write to file: {}", tmp_path.display()))?;
|
||||||
|
|
||||||
// Set the owner of the tmpfile (UNIX only).
|
// Set the owner of the tmpfile (UNIX only).
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
@ -224,16 +226,21 @@ fn rename(from: impl AsRef<Path>, to: impl AsRef<Path>) -> Result<()> {
|
|||||||
|
|
||||||
loop {
|
loop {
|
||||||
match fs::rename(from, to) {
|
match fs::rename(from, to) {
|
||||||
Err(e) if e.kind() == io::ErrorKind::PermissionDenied && attempts < MAX_ATTEMPTS => attempts += 1,
|
Err(e) if e.kind() == io::ErrorKind::PermissionDenied && attempts < MAX_ATTEMPTS => {
|
||||||
|
attempts += 1
|
||||||
|
}
|
||||||
result => {
|
result => {
|
||||||
break result.with_context(|| format!("could not rename file: {} -> {}", from.display(), to.display()))
|
break result.with_context(|| {
|
||||||
|
format!("could not rename file: {} -> {}", from.display(), to.display())
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn canonicalize(path: impl AsRef<Path>) -> Result<PathBuf> {
|
pub fn canonicalize(path: impl AsRef<Path>) -> Result<PathBuf> {
|
||||||
dunce::canonicalize(&path).with_context(|| format!("could not resolve path: {}", path.as_ref().display()))
|
dunce::canonicalize(&path)
|
||||||
|
.with_context(|| format!("could not resolve path: {}", path.as_ref().display()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn current_dir() -> Result<PathBuf> {
|
pub fn current_dir() -> Result<PathBuf> {
|
||||||
@ -241,8 +248,10 @@ pub fn current_dir() -> Result<PathBuf> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn current_time() -> Result<Epoch> {
|
pub fn current_time() -> Result<Epoch> {
|
||||||
let current_time =
|
let current_time = SystemTime::now()
|
||||||
SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).context("system clock set to invalid time")?.as_secs();
|
.duration_since(SystemTime::UNIX_EPOCH)
|
||||||
|
.context("system clock set to invalid time")?
|
||||||
|
.as_secs();
|
||||||
|
|
||||||
Ok(current_time)
|
Ok(current_time)
|
||||||
}
|
}
|
||||||
@ -252,8 +261,8 @@ pub fn path_to_str(path: &impl AsRef<Path>) -> Result<&str> {
|
|||||||
path.to_str().with_context(|| format!("invalid unicode in path: {}", path.display()))
|
path.to_str().with_context(|| format!("invalid unicode in path: {}", path.display()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the absolute version of a path. Like [`std::path::Path::canonicalize`], but doesn't
|
/// Returns the absolute version of a path. Like
|
||||||
/// resolve symlinks.
|
/// [`std::path::Path::canonicalize`], but doesn't resolve symlinks.
|
||||||
pub fn resolve_path(path: impl AsRef<Path>) -> Result<PathBuf> {
|
pub fn resolve_path(path: impl AsRef<Path>) -> Result<PathBuf> {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
let base_path;
|
let base_path;
|
||||||
@ -271,7 +280,9 @@ pub fn resolve_path(path: impl AsRef<Path>) -> Result<PathBuf> {
|
|||||||
|
|
||||||
match components.next() {
|
match components.next() {
|
||||||
Some(Component::Prefix(prefix)) => match prefix.kind() {
|
Some(Component::Prefix(prefix)) => match prefix.kind() {
|
||||||
Prefix::Disk(drive_letter) | Prefix::VerbatimDisk(drive_letter) => Some(drive_letter),
|
Prefix::Disk(drive_letter) | Prefix::VerbatimDisk(drive_letter) => {
|
||||||
|
Some(drive_letter)
|
||||||
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
},
|
},
|
||||||
_ => None,
|
_ => None,
|
||||||
@ -324,8 +335,9 @@ pub fn resolve_path(path: impl AsRef<Path>) -> Result<PathBuf> {
|
|||||||
components.next();
|
components.next();
|
||||||
|
|
||||||
let current_dir = env::current_dir()?;
|
let current_dir = env::current_dir()?;
|
||||||
let drive_letter = get_drive_letter(¤t_dir)
|
let drive_letter = get_drive_letter(¤t_dir).with_context(|| {
|
||||||
.with_context(|| format!("could not get drive letter: {}", current_dir.display()))?;
|
format!("could not get drive letter: {}", current_dir.display())
|
||||||
|
})?;
|
||||||
base_path = get_drive_path(drive_letter);
|
base_path = get_drive_path(drive_letter);
|
||||||
stack.extend(base_path.components());
|
stack.extend(base_path.components());
|
||||||
}
|
}
|
||||||
@ -361,9 +373,5 @@ pub fn resolve_path(path: impl AsRef<Path>) -> Result<PathBuf> {
|
|||||||
/// Convert a string to lowercase, with a fast path for ASCII strings.
|
/// Convert a string to lowercase, with a fast path for ASCII strings.
|
||||||
pub fn to_lowercase(s: impl AsRef<str>) -> String {
|
pub fn to_lowercase(s: impl AsRef<str>) -> String {
|
||||||
let s = s.as_ref();
|
let s = s.as_ref();
|
||||||
if s.is_ascii() {
|
if s.is_ascii() { s.to_ascii_lowercase() } else { s.to_lowercase() }
|
||||||
s.to_ascii_lowercase()
|
|
||||||
} else {
|
|
||||||
s.to_lowercase()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -6,12 +6,17 @@ use assert_cmd::Command;
|
|||||||
#[test]
|
#[test]
|
||||||
fn completions_bash() {
|
fn completions_bash() {
|
||||||
let source = include_str!("../contrib/completions/zoxide.bash");
|
let source = include_str!("../contrib/completions/zoxide.bash");
|
||||||
Command::new("bash").args(["--noprofile", "--norc", "-c", source]).assert().success().stdout("").stderr("");
|
Command::new("bash")
|
||||||
|
.args(["--noprofile", "--norc", "-c", source])
|
||||||
|
.assert()
|
||||||
|
.success()
|
||||||
|
.stdout("")
|
||||||
|
.stderr("");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Elvish: the completions file uses editor commands to add completions to the shell. However,
|
// Elvish: the completions file uses editor commands to add completions to the
|
||||||
// Elvish does not support running editor commands from a script, so we can't create a test for
|
// shell. However, Elvish does not support running editor commands from a
|
||||||
// this. See: https://github.com/elves/elvish/issues/1299
|
// script, so we can't create a test for this. See: https://github.com/elves/elvish/issues/1299
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn completions_fish() {
|
fn completions_fish() {
|
||||||
|
@ -9,8 +9,11 @@ use ignore::Walk;
|
|||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||||
let dir = dir.parent().with_context(|| format!("could not find workspace root: {}", dir.display()))?;
|
let dir = dir
|
||||||
env::set_current_dir(dir).with_context(|| format!("could not set current directory: {}", dir.display()))?;
|
.parent()
|
||||||
|
.with_context(|| format!("could not find workspace root: {}", dir.display()))?;
|
||||||
|
env::set_current_dir(dir)
|
||||||
|
.with_context(|| format!("could not set current directory: {}", dir.display()))?;
|
||||||
let nix_enabled = enable_nix();
|
let nix_enabled = enable_nix();
|
||||||
|
|
||||||
let app = App::parse();
|
let app = App::parse();
|
||||||
@ -55,7 +58,10 @@ impl CommandExt for &mut Command {
|
|||||||
|
|
||||||
fn run_ci(nix_enabled: bool) -> Result<()> {
|
fn run_ci(nix_enabled: bool) -> Result<()> {
|
||||||
// Run cargo-clippy.
|
// Run cargo-clippy.
|
||||||
Command::new("cargo").args(["clippy", "--all-features", "--all-targets"]).args(["--", "-Dwarnings"]).run()?;
|
Command::new("cargo")
|
||||||
|
.args(["clippy", "--all-features", "--all-targets"])
|
||||||
|
.args(["--", "-Dwarnings"])
|
||||||
|
.run()?;
|
||||||
run_fmt(nix_enabled, true)?;
|
run_fmt(nix_enabled, true)?;
|
||||||
run_lint(nix_enabled)?;
|
run_lint(nix_enabled)?;
|
||||||
run_tests(nix_enabled, "")
|
run_tests(nix_enabled, "")
|
||||||
@ -63,8 +69,9 @@ fn run_ci(nix_enabled: bool) -> Result<()> {
|
|||||||
|
|
||||||
fn run_fmt(nix_enabled: bool, check: bool) -> Result<()> {
|
fn run_fmt(nix_enabled: bool, check: bool) -> Result<()> {
|
||||||
// Run cargo-fmt.
|
// Run cargo-fmt.
|
||||||
// let check_args: &[&str] = if check { &["--check", "--files-with-diff"] } else { &[] };
|
// let check_args: &[&str] = if check {&["--check", "--files-with-diff"] } else
|
||||||
// Command::new("cargo").args(&["fmt", "--all", "--"]).args(check_args).run()?;
|
// { &[] }; Command::new("cargo").args(&["fmt", "--all",
|
||||||
|
// "--"]).args(check_args).run()?;
|
||||||
|
|
||||||
// Run nixfmt.
|
// Run nixfmt.
|
||||||
if nix_enabled {
|
if nix_enabled {
|
||||||
@ -119,7 +126,8 @@ fn enable_nix() -> bool {
|
|||||||
if nix_enabled {
|
if nix_enabled {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
let nix_detected = Command::new("nix-shell").arg("--version").status().map(|s| s.success()).unwrap_or(false);
|
let nix_detected =
|
||||||
|
Command::new("nix-shell").arg("--version").status().map(|s| s.success()).unwrap_or(false);
|
||||||
if !nix_detected {
|
if !nix_detected {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -128,6 +136,9 @@ fn enable_nix() -> bool {
|
|||||||
let args = env::args();
|
let args = env::args();
|
||||||
let cmd = shell_words::join(args);
|
let cmd = shell_words::join(args);
|
||||||
|
|
||||||
let status = Command::new("nix-shell").args(["--pure", "--run", &cmd, "--", "shell.nix"]).status().unwrap();
|
let status = Command::new("nix-shell")
|
||||||
|
.args(["--pure", "--run", &cmd, "--", "shell.nix"])
|
||||||
|
.status()
|
||||||
|
.unwrap();
|
||||||
process::exit(status.code().unwrap_or(1));
|
process::exit(status.code().unwrap_or(1));
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user