This commit is contained in:
Taha Mahmoud 2026-08-13 16:44:23 +03:00 committed by GitHub
commit 9093d282ab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 696 additions and 87 deletions

View File

@ -39,6 +39,17 @@ _arguments "${_arguments_options[@]}" : \
'*::paths:_files -/' \
&& ret=0
;;
(add-alias)
_arguments "${_arguments_options[@]}" : \
'-p+[Path to add aliases to]:PATH:_files -/' \
'--path=[Path to add aliases to]:PATH:_files -/' \
'-h[Print help]' \
'--help[Print help]' \
'-V[Print version]' \
'--version[Print version]' \
'*::aliases:_default' \
&& ret=0
;;
(edit)
_arguments "${_arguments_options[@]}" : \
'-h[Print help]' \
@ -193,6 +204,7 @@ _arguments "${_arguments_options[@]}" : \
'(-i --interactive)--list[List all matching directories]' \
'-s[Print score with results]' \
'--score[Print score with results]' \
'--aliases[Print aliases with results]' \
'-h[Print help]' \
'--help[Print help]' \
'-V[Print version]' \
@ -208,6 +220,17 @@ _arguments "${_arguments_options[@]}" : \
'--version[Print version]' \
'*::paths:_files -/' \
&& ret=0
;;
(remove-alias)
_arguments "${_arguments_options[@]}" : \
'-p+[Path to remove aliases from]:PATH:_files -/' \
'--path=[Path to remove aliases from]:PATH:_files -/' \
'-h[Print help]' \
'--help[Print help]' \
'-V[Print version]' \
'--version[Print version]' \
'*::aliases:_default' \
&& ret=0
;;
esac
;;
@ -218,11 +241,13 @@ esac
_zoxide_commands() {
local commands; commands=(
'add:Add a new directory or increment its rank' \
'add-alias:Add aliases for a directory' \
'edit:Edit the database' \
'import:Import entries from another application' \
'init:Generate shell configuration' \
'query:Search for a directory in the database' \
'remove:Remove a directory from the database' \
'remove-alias:Remove aliases from a directory' \
)
_describe -t commands 'zoxide commands' commands "$@"
}
@ -231,6 +256,11 @@ _zoxide__subcmd__add_commands() {
local commands; commands=()
_describe -t commands 'zoxide add commands' commands "$@"
}
(( $+functions[_zoxide__subcmd__add-alias_commands] )) ||
_zoxide__subcmd__add-alias_commands() {
local commands; commands=()
_describe -t commands 'zoxide add-alias commands' commands "$@"
}
(( $+functions[_zoxide__subcmd__edit_commands] )) ||
_zoxide__subcmd__edit_commands() {
local commands; commands=(
@ -318,6 +348,11 @@ _zoxide__subcmd__remove_commands() {
local commands; commands=()
_describe -t commands 'zoxide remove commands' commands "$@"
}
(( $+functions[_zoxide__subcmd__remove-alias_commands] )) ||
_zoxide__subcmd__remove-alias_commands() {
local commands; commands=()
_describe -t commands 'zoxide remove-alias commands' commands "$@"
}
if [ "$funcstack[1]" = "_zoxide" ]; then
_zoxide "$@"

View File

@ -26,11 +26,13 @@ Register-ArgumentCompleter -Native -CommandName 'zoxide' -ScriptBlock {
[CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version')
[CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version')
[CompletionResult]::new('add', 'add', [CompletionResultType]::ParameterValue, 'Add a new directory or increment its rank')
[CompletionResult]::new('add-alias', 'add-alias', [CompletionResultType]::ParameterValue, 'Add aliases for a directory')
[CompletionResult]::new('edit', 'edit', [CompletionResultType]::ParameterValue, 'Edit the database')
[CompletionResult]::new('import', 'import', [CompletionResultType]::ParameterValue, 'Import entries from another application')
[CompletionResult]::new('init', 'init', [CompletionResultType]::ParameterValue, 'Generate shell configuration')
[CompletionResult]::new('query', 'query', [CompletionResultType]::ParameterValue, 'Search for a directory in the database')
[CompletionResult]::new('remove', 'remove', [CompletionResultType]::ParameterValue, 'Remove a directory from the database')
[CompletionResult]::new('remove-alias', 'remove-alias', [CompletionResultType]::ParameterValue, 'Remove aliases from a directory')
break
}
'zoxide;add' {
@ -42,6 +44,15 @@ Register-ArgumentCompleter -Native -CommandName 'zoxide' -ScriptBlock {
[CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version')
break
}
'zoxide;add-alias' {
[CompletionResult]::new('-p', '-p', [CompletionResultType]::ParameterName, 'Path to add aliases to')
[CompletionResult]::new('--path', '--path', [CompletionResultType]::ParameterName, 'Path to add aliases to')
[CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version')
[CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version')
break
}
'zoxide;edit' {
[CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')
@ -164,6 +175,7 @@ Register-ArgumentCompleter -Native -CommandName 'zoxide' -ScriptBlock {
[CompletionResult]::new('--list', '--list', [CompletionResultType]::ParameterName, 'List all matching directories')
[CompletionResult]::new('-s', '-s', [CompletionResultType]::ParameterName, 'Print score with results')
[CompletionResult]::new('--score', '--score', [CompletionResultType]::ParameterName, 'Print score with results')
[CompletionResult]::new('--aliases', '--aliases', [CompletionResultType]::ParameterName, 'Print aliases with results')
[CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version')
@ -177,6 +189,15 @@ Register-ArgumentCompleter -Native -CommandName 'zoxide' -ScriptBlock {
[CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version')
break
}
'zoxide;remove-alias' {
[CompletionResult]::new('-p', '-p', [CompletionResultType]::ParameterName, 'Path to remove aliases from')
[CompletionResult]::new('--path', '--path', [CompletionResultType]::ParameterName, 'Path to remove aliases from')
[CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version')
[CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version')
break
}
})
$completions.Where{ $_.CompletionText -like "$wordToComplete*" } |

View File

@ -19,6 +19,9 @@ _zoxide() {
zoxide,add)
cmd="zoxide__subcmd__add"
;;
zoxide,add-alias)
cmd="zoxide__subcmd__add__subcmd__alias"
;;
zoxide,edit)
cmd="zoxide__subcmd__edit"
;;
@ -34,6 +37,9 @@ _zoxide() {
zoxide,remove)
cmd="zoxide__subcmd__remove"
;;
zoxide,remove-alias)
cmd="zoxide__subcmd__remove__subcmd__alias"
;;
zoxide__subcmd__edit,decrement)
cmd="zoxide__subcmd__edit__subcmd__decrement"
;;
@ -71,7 +77,7 @@ _zoxide() {
case "${cmd}" in
zoxide)
opts="-h -V --help --version add edit import init query remove"
opts="-h -V --help --version add add-alias edit import init query remove remove-alias"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
@ -106,6 +112,34 @@ _zoxide() {
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
zoxide__subcmd__add__subcmd__alias)
opts="-p -h -V --path --help --version <ALIASES>..."
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
fi
case "${prev}" in
--path)
COMPREPLY=()
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
compopt -o plusdirs
fi
return 0
;;
-p)
COMPREPLY=()
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
compopt -o plusdirs
fi
return 0
;;
*)
COMPREPLY=()
;;
esac
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
zoxide__subcmd__edit)
opts="-h -V --help --version decrement delete increment reload"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
@ -297,7 +331,7 @@ _zoxide() {
return 0
;;
zoxide__subcmd__query)
opts="-a -i -l -s -h -V --all --interactive --list --score --exclude --base-dir --help --version [KEYWORDS]..."
opts="-a -i -l -s -h -V --all --interactive --list --score --aliases --exclude --base-dir --help --version [KEYWORDS]..."
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
@ -338,6 +372,34 @@ _zoxide() {
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
zoxide__subcmd__remove__subcmd__alias)
opts="-p -h -V --path --help --version <ALIASES>..."
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
fi
case "${prev}" in
--path)
COMPREPLY=()
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
compopt -o plusdirs
fi
return 0
;;
-p)
COMPREPLY=()
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
compopt -o plusdirs
fi
return 0
;;
*)
COMPREPLY=()
;;
esac
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
esac
}

View File

@ -23,11 +23,13 @@ set edit:completion:arg-completer[zoxide] = {|@words|
cand -V 'Print version'
cand --version 'Print version'
cand add 'Add a new directory or increment its rank'
cand add-alias 'Add aliases for a directory'
cand edit 'Edit the database'
cand import 'Import entries from another application'
cand init 'Generate shell configuration'
cand query 'Search for a directory in the database'
cand remove 'Remove a directory from the database'
cand remove-alias 'Remove aliases from a directory'
}
&'zoxide;add'= {
cand -s 'The rank to increment the entry if it exists or initialize it with if it doesn''t'
@ -37,6 +39,14 @@ set edit:completion:arg-completer[zoxide] = {|@words|
cand -V 'Print version'
cand --version 'Print version'
}
&'zoxide;add-alias'= {
cand -p 'Path to add aliases to'
cand --path 'Path to add aliases to'
cand -h 'Print help'
cand --help 'Print help'
cand -V 'Print version'
cand --version 'Print version'
}
&'zoxide;edit'= {
cand -h 'Print help'
cand --help 'Print help'
@ -146,6 +156,7 @@ set edit:completion:arg-completer[zoxide] = {|@words|
cand --list 'List all matching directories'
cand -s 'Print score with results'
cand --score 'Print score with results'
cand --aliases 'Print aliases with results'
cand -h 'Print help'
cand --help 'Print help'
cand -V 'Print version'
@ -157,6 +168,14 @@ set edit:completion:arg-completer[zoxide] = {|@words|
cand -V 'Print version'
cand --version 'Print version'
}
&'zoxide;remove-alias'= {
cand -p 'Path to remove aliases from'
cand --path 'Path to remove aliases from'
cand -h 'Print help'
cand --help 'Print help'
cand -V 'Print version'
cand --version 'Print version'
}
]
$completions[$command]
}

View File

@ -27,14 +27,19 @@ end
complete -c zoxide -n "__fish_zoxide_needs_command" -s h -l help -d 'Print help'
complete -c zoxide -n "__fish_zoxide_needs_command" -s V -l version -d 'Print version'
complete -c zoxide -n "__fish_zoxide_needs_command" -f -a "add" -d 'Add a new directory or increment its rank'
complete -c zoxide -n "__fish_zoxide_needs_command" -f -a "add-alias" -d 'Add aliases for a directory'
complete -c zoxide -n "__fish_zoxide_needs_command" -f -a "edit" -d 'Edit the database'
complete -c zoxide -n "__fish_zoxide_needs_command" -f -a "import" -d 'Import entries from another application'
complete -c zoxide -n "__fish_zoxide_needs_command" -f -a "init" -d 'Generate shell configuration'
complete -c zoxide -n "__fish_zoxide_needs_command" -f -a "query" -d 'Search for a directory in the database'
complete -c zoxide -n "__fish_zoxide_needs_command" -f -a "remove" -d 'Remove a directory from the database'
complete -c zoxide -n "__fish_zoxide_needs_command" -f -a "remove-alias" -d 'Remove aliases from a directory'
complete -c zoxide -n "__fish_zoxide_using_subcommand add" -s s -l score -d 'The rank to increment the entry if it exists or initialize it with if it doesn\'t' -r
complete -c zoxide -n "__fish_zoxide_using_subcommand add" -s h -l help -d 'Print help'
complete -c zoxide -n "__fish_zoxide_using_subcommand add" -s V -l version -d 'Print version'
complete -c zoxide -n "__fish_zoxide_using_subcommand add-alias" -s p -l path -d 'Path to add aliases to' -r -f -a "(__fish_complete_directories)"
complete -c zoxide -n "__fish_zoxide_using_subcommand add-alias" -s h -l help -d 'Print help'
complete -c zoxide -n "__fish_zoxide_using_subcommand add-alias" -s V -l version -d 'Print version'
complete -c zoxide -n "__fish_zoxide_using_subcommand edit; and not __fish_seen_subcommand_from decrement delete increment reload" -s h -l help -d 'Print help'
complete -c zoxide -n "__fish_zoxide_using_subcommand edit; and not __fish_seen_subcommand_from decrement delete increment reload" -s V -l version -d 'Print version'
complete -c zoxide -n "__fish_zoxide_using_subcommand edit; and not __fish_seen_subcommand_from decrement delete increment reload" -f -a "decrement"
@ -89,7 +94,11 @@ complete -c zoxide -n "__fish_zoxide_using_subcommand query" -s a -l all -d 'Sho
complete -c zoxide -n "__fish_zoxide_using_subcommand query" -s i -l interactive -d 'Use interactive selection'
complete -c zoxide -n "__fish_zoxide_using_subcommand query" -s l -l list -d 'List all matching directories'
complete -c zoxide -n "__fish_zoxide_using_subcommand query" -s s -l score -d 'Print score with results'
complete -c zoxide -n "__fish_zoxide_using_subcommand query" -l aliases -d 'Print aliases with results'
complete -c zoxide -n "__fish_zoxide_using_subcommand query" -s h -l help -d 'Print help'
complete -c zoxide -n "__fish_zoxide_using_subcommand query" -s V -l version -d 'Print version'
complete -c zoxide -n "__fish_zoxide_using_subcommand remove" -s h -l help -d 'Print help'
complete -c zoxide -n "__fish_zoxide_using_subcommand remove" -s V -l version -d 'Print version'
complete -c zoxide -n "__fish_zoxide_using_subcommand remove-alias" -s p -l path -d 'Path to remove aliases from' -r -f -a "(__fish_complete_directories)"
complete -c zoxide -n "__fish_zoxide_using_subcommand remove-alias" -s h -l help -d 'Print help'
complete -c zoxide -n "__fish_zoxide_using_subcommand remove-alias" -s V -l version -d 'Print version'

View File

@ -14,6 +14,14 @@ module completions {
...paths: path
]
# Add aliases for a directory
export extern "zoxide add-alias" [
--path(-p): path # Path to add aliases to
--help(-h) # Print help
--version(-V) # Print version
...aliases: string
]
# Edit the database
export extern "zoxide edit" [
--help(-h) # Print help
@ -116,6 +124,7 @@ module completions {
--interactive(-i) # Use interactive selection
--list(-l) # List all matching directories
--score(-s) # Print score with results
--aliases # Print aliases with results
--exclude: path # Exclude the current directory
--base-dir: path # Only search within this directory
--help(-h) # Print help
@ -130,6 +139,14 @@ module completions {
...paths: path
]
# Remove aliases from a directory
export extern "zoxide remove-alias" [
--path(-p): path # Path to remove aliases from
--help(-h) # Print help
--version(-V) # Print version
...aliases: string
]
}
export use completions *

View File

@ -30,6 +30,33 @@ const completion: Fig.Spec = {
template: "folders",
},
},
{
name: "add-alias",
description: "Add aliases for a directory",
options: [
{
name: ["-p", "--path"],
description: "Path to add aliases to",
isRepeatable: true,
args: {
name: "path",
template: "folders",
},
},
{
name: ["-h", "--help"],
description: "Print help",
},
{
name: ["-V", "--version"],
description: "Print version",
},
],
args: {
name: "aliases",
isVariadic: true,
},
},
{
name: "edit",
description: "Edit the database",
@ -342,6 +369,10 @@ const completion: Fig.Spec = {
name: ["-s", "--score"],
description: "Print score with results",
},
{
name: "--aliases",
description: "Print aliases with results",
},
{
name: ["-h", "--help"],
description: "Print help",
@ -377,6 +408,33 @@ const completion: Fig.Spec = {
template: "folders",
},
},
{
name: "remove-alias",
description: "Remove aliases from a directory",
options: [
{
name: ["-p", "--path"],
description: "Path to remove aliases from",
isRepeatable: true,
args: {
name: "path",
template: "folders",
},
},
{
name: ["-h", "--help"],
description: "Print help",
},
{
name: ["-V", "--version"],
description: "Print version",
},
],
args: {
name: "aliases",
isVariadic: true,
},
},
],
options: [
{

43
src/cmd/add_alias.rs Normal file
View File

@ -0,0 +1,43 @@
use std::path::Path;
use anyhow::{Result, bail};
use crate::cmd::{AddAlias, Run};
use crate::db::Database;
use crate::{config, util};
impl Run for AddAlias {
fn run(&self) -> Result<()> {
// These characters can't be printed cleanly to a single line, so they can cause
// confusion when writing to stdout.
const EXCLUDE_CHARS: &[char] = &['\n', '\r'];
let exclude_dirs = config::exclude_dirs()?;
let max_age = config::maxage()?;
let now = util::current_time()?;
let mut db = Database::open()?;
let path =
if config::resolve_symlinks() { util::canonicalize } else { util::resolve_path }(
&self.path,
)?;
let path = util::path_to_str(&path)?;
// 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)) {
return Ok(());
}
if !Path::new(path).is_dir() {
bail!("not a directory: {path}");
}
db.add_alias_update(path, self.aliases.iter(), now);
if db.dirty() {
db.age(max_age);
}
db.save()
}
}

View File

@ -42,11 +42,13 @@ https://github.com/ajeetdsouza/zoxide
)]
pub enum Cmd {
Add(Add),
AddAlias(AddAlias),
Edit(Edit),
Import(Import),
Init(Init),
Query(Query),
Remove(Remove),
RemoveAlias(RemoveAlias),
}
/// Add a new directory or increment its rank
@ -65,6 +67,21 @@ pub struct Add {
pub score: Option<f64>,
}
/// Add aliases for a directory
#[derive(Debug, Parser)]
#[clap(
author,
help_template = HelpTemplate,
)]
pub struct AddAlias {
#[clap(num_args = 1.., required = true)]
pub aliases: Vec<String>,
/// Path to add aliases to
#[clap(short, long, required = true, value_hint = ValueHint::DirPath)]
pub path: PathBuf,
}
/// Edit the database
#[derive(Debug, Parser)]
#[clap(
@ -190,6 +207,10 @@ pub struct Query {
#[clap(long, short)]
pub score: bool,
/// Print aliases with results
#[clap(long)]
pub aliases: bool,
/// Exclude the current directory
#[clap(long, value_hint = ValueHint::DirPath, value_name = "path")]
pub exclude: Option<String>,
@ -209,3 +230,18 @@ pub struct Remove {
#[clap(value_hint = ValueHint::DirPath)]
pub paths: Vec<String>,
}
/// Remove aliases from a directory
#[derive(Debug, Parser)]
#[clap(
author,
help_template = HelpTemplate,
)]
pub struct RemoveAlias {
#[clap(num_args = 1.., required = true)]
pub aliases: Vec<String>,
/// Path to remove aliases from
#[clap(short, long, required = true, value_hint = ValueHint::DirPath)]
pub path: String,
}

View File

@ -26,8 +26,12 @@ impl Run for Edit {
let stdout = &mut io::stdout().lock();
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_aliases(true).with_separator('\t')
)
.pipe_exit("fzf")?;
}
Ok(())
}
@ -53,9 +57,9 @@ impl Edit {
"--bind=\
btab:up,\
ctrl-r:reload(zoxide edit reload),\
ctrl-d:reload(zoxide edit delete {2..}),\
ctrl-w:reload(zoxide edit increment {2..}),\
ctrl-s:reload(zoxide edit decrement {2..}),\
ctrl-d:reload(zoxide edit delete {3..}),\
ctrl-w:reload(zoxide edit increment {3..}),\
ctrl-s:reload(zoxide edit decrement {3..}),\
ctrl-z:ignore,\
double-click:ignore,\
enter:abort,\
@ -70,7 +74,7 @@ tab:down",
ctrl-r:reload \tctrl-d:delete
ctrl-w:increment\tctrl-s:decrement
SCORE\tPATH",
SCORE\tALIASES\tPATH",
"--info=inline",
"--layout=reverse",
"--padding=1,0,0,0",

View File

@ -1,10 +1,12 @@
mod add;
mod add_alias;
mod cmd;
mod edit;
mod import;
mod init;
mod query;
mod remove;
mod remove_alias;
use anyhow::Result;
@ -18,11 +20,13 @@ impl Run for Cmd {
fn run(&self) -> Result<()> {
match self {
Cmd::Add(cmd) => cmd.run(),
Cmd::AddAlias(cmd) => cmd.run(),
Cmd::Edit(cmd) => cmd.run(),
Cmd::Import(cmd) => cmd.run(),
Cmd::Init(cmd) => cmd.run(),
Cmd::Query(cmd) => cmd.run(),
Cmd::Remove(cmd) => cmd.run(),
Cmd::RemoveAlias(cmd) => cmd.run(),
}
}
}

View File

@ -35,7 +35,8 @@ impl Query {
match stream.next() {
Some(dir) if Some(dir.path.as_ref()) == self.exclude.as_deref() => continue,
Some(dir) => {
if let Some(selection) = fzf.write(dir, now)? {
// Always enable aliases for interactive queries
if let Some(selection) = fzf.write(dir, now, true)? {
break selection;
}
}
@ -45,9 +46,17 @@ impl Query {
if self.score {
print!("{selection}");
} else {
} else if self.aliases {
let path = selection.get(7..).context("could not read selection from fzf")?;
print!("{path}");
} else {
let path = selection
.get(7..)
.map(|path| {
path.get(path.find('\t').map(|idx| idx + 1).unwrap_or(0)..).unwrap_or(path)
})
.context("could not read selection from fzf")?;
print!("{path}");
}
Ok(())
}
@ -59,6 +68,7 @@ impl Query {
continue;
}
let dir = if self.score { dir.display().with_score(now) } else { dir.display() };
let dir = if self.aliases { dir.with_aliases(self.aliases) } else { dir };
writeln!(handle, "{dir}").pipe_exit("stdout")?;
}
Ok(())
@ -73,6 +83,7 @@ impl Query {
}
let dir = if self.score { dir.display().with_score(now) } else { dir.display() };
let dir = if self.aliases { dir.with_aliases(self.aliases) } else { dir };
writeln!(handle, "{dir}").pipe_exit("stdout")
}

21
src/cmd/remove_alias.rs Normal file
View File

@ -0,0 +1,21 @@
use anyhow::{Result, bail};
use crate::cmd::{RemoveAlias, Run};
use crate::db::Database;
use crate::util;
impl Run for RemoveAlias {
fn run(&self) -> Result<()> {
let mut db = Database::open()?;
if !db.remove_alias(&self.path, self.aliases.iter()) {
let path_abs = util::resolve_path(&self.path)?;
let path_abs = util::path_to_str(&path_abs)?;
if path_abs == self.path || !db.remove_alias(path_abs, self.aliases.iter()) {
bail!("path not found in database: {}", self.path)
}
}
db.save()
}
}

View File

@ -1,4 +1,5 @@
use std::borrow::Cow;
use std::collections::HashSet;
use std::fmt::{self, Display, Formatter};
use serde::{Deserialize, Serialize};
@ -6,19 +7,35 @@ use serde::{Deserialize, Serialize};
use crate::util::{DAY, HOUR, WEEK};
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Dir<'a> {
pub struct DirV4<'a> {
#[serde(borrow)]
pub path: Cow<'a, str>,
pub rank: Rank,
pub last_accessed: Epoch,
#[serde(borrow)]
pub aliases: HashSet<Cow<'a, str>>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct DirV3<'a> {
#[serde(borrow)]
pub path: Cow<'a, str>,
pub rank: Rank,
pub last_accessed: Epoch,
}
impl Dir<'_> {
pub fn display(&self) -> DirDisplay<'_> {
DirDisplay::new(self)
pub trait Dir {
fn path(&self) -> &str;
fn score(&self, now: Epoch) -> Rank;
fn aliases(&self) -> impl Iterator<Item = impl AsRef<str>>;
}
impl Dir for DirV4<'_> {
fn path(&self) -> &str {
&self.path
}
pub fn score(&self, now: Epoch) -> Rank {
fn score(&self, now: Epoch) -> Rank {
// The older the entry, the lesser its importance.
let duration = now.saturating_sub(self.last_accessed);
if duration < HOUR {
@ -31,17 +48,53 @@ impl Dir<'_> {
self.rank * 0.25
}
}
fn aliases(&self) -> impl Iterator<Item = impl AsRef<str>> {
self.aliases.iter()
}
}
pub struct DirDisplay<'a> {
dir: &'a Dir<'a>,
impl DirV4<'_> {
pub fn display(&self) -> DirDisplay<'_, Self> {
DirDisplay::new(self)
}
}
impl Dir for DirV3<'_> {
fn path(&self) -> &str {
&self.path
}
fn score(&self, now: Epoch) -> Rank {
// The older the entry, the lesser its importance.
let duration = now.saturating_sub(self.last_accessed);
if duration < HOUR {
self.rank * 4.0
} else if duration < DAY {
self.rank * 2.0
} else if duration < WEEK {
self.rank * 0.5
} else {
self.rank * 0.25
}
}
fn aliases(&self) -> impl Iterator<Item = impl AsRef<str>> {
let arr: &[&str] = &[];
arr.iter()
}
}
pub struct DirDisplay<'a, T: Dir> {
dir: &'a T,
now: Option<Epoch>,
separator: char,
aliases: bool,
}
impl<'a> DirDisplay<'a> {
fn new(dir: &'a Dir) -> Self {
Self { dir, separator: ' ', now: None }
impl<'a, T: Dir> DirDisplay<'a, T> {
fn new(dir: &'a T) -> Self {
Self { dir, separator: ' ', now: None, aliases: false }
}
pub fn with_score(mut self, now: Epoch) -> Self {
@ -53,15 +106,28 @@ impl<'a> DirDisplay<'a> {
self.separator = separator;
self
}
pub fn with_aliases(mut self, enable: bool) -> Self {
self.aliases = enable;
self
}
}
impl Display for DirDisplay<'_> {
impl<'a, T: Dir> Display for DirDisplay<'a, T> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
if let Some(now) = self.now {
let score = self.dir.score(now).clamp(0.0, 9999.0);
write!(f, "{score:>6.1}{}", self.separator)?;
}
write!(f, "{}", self.dir.path)
if self.aliases {
for alias in self.dir.aliases() {
write!(f, "{} ", alias.as_ref())?;
}
write!(f, "{}", self.separator)?;
}
write!(f, "{}", self.dir.path())
}
}

View File

@ -1,6 +1,7 @@
mod dir;
mod stream;
use std::collections::HashSet;
use std::path::{Path, PathBuf};
use std::{fs, io};
@ -8,7 +9,8 @@ use anyhow::{Context, Result, bail};
use bincode::Options;
use ouroboros::self_referencing;
pub use crate::db::dir::{Dir, Epoch, Rank};
use crate::db::dir::{Dir, DirV3};
pub use crate::db::dir::{DirV4, Epoch, Rank};
pub use crate::db::stream::{Stream, StreamOptions};
use crate::{config, util};
@ -18,12 +20,13 @@ pub struct Database {
bytes: Vec<u8>,
#[borrows(bytes)]
#[covariant]
pub dirs: Vec<Dir<'this>>,
pub dirs: Vec<DirV4<'this>>,
dirty: bool,
}
impl Database {
const VERSION: u32 = 3;
const PREV_VERSION: u32 = 3;
const VERSION: u32 = 4;
pub fn open() -> Result<Self> {
let data_dir = config::data_dir()?;
@ -67,10 +70,15 @@ impl Database {
/// Increments the rank of a directory, or creates it if it does not exist.
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()) {
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 })
Some(dir) => {
dir.rank = (dir.rank + by).max(0.0);
}
None => dirs.push(DirV4 {
path: path.into().into(),
rank: by.max(0.0),
last_accessed: now,
aliases: HashSet::new(),
}),
});
self.with_dirty_mut(|dirty| *dirty = true);
}
@ -81,7 +89,12 @@ impl Database {
/// afterward.
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 })
dirs.push(DirV4 {
path: path.into().into(),
rank,
last_accessed: now,
aliases: HashSet::new(),
})
});
self.with_dirty_mut(|dirty| *dirty = true);
}
@ -94,13 +107,50 @@ impl Database {
dir.rank = (dir.rank + by).max(0.0);
dir.last_accessed = now;
}
None => {
dirs.push(Dir { path: path.into().into(), rank: by.max(0.0), last_accessed: now })
}
None => dirs.push(DirV4 {
path: path.into().into(),
rank: by.max(0.0),
last_accessed: now,
aliases: HashSet::new(),
}),
});
self.with_dirty_mut(|dirty| *dirty = true);
}
/// Adds aliases to a directory and updates its last_accessed, or
/// creates it and adds aliases to it if it does not exist.
pub fn add_alias_update(
&mut self,
path: impl AsRef<str> + Into<String>,
aliases: impl Iterator<Item = impl AsRef<str> + Into<String>>,
now: Epoch,
) {
let mut is_dirty = false;
self.with_dirs_mut(|dirs| match dirs.iter_mut().find(|dir| dir.path == path.as_ref()) {
Some(dir) => {
let starting_len = dir.aliases.len();
dir.aliases.extend(aliases.map(|alias| alias.into().into()));
dir.last_accessed = now;
is_dirty = dir.aliases.len() > starting_len;
}
None => {
let mut set = HashSet::new();
set.extend(aliases.map(|alias| alias.into().into()));
dirs.push(DirV4 {
path: path.into().into(),
rank: 0.0,
last_accessed: now,
aliases: set,
});
is_dirty = true;
}
});
self.with_dirty_mut(|dirty| *dirty |= is_dirty);
}
/// Removes the directory with `path` from the store. This does not preserve
/// ordering, but is O(1).
pub fn remove(&mut self, path: impl AsRef<str>) -> bool {
@ -118,6 +168,28 @@ impl Database {
self.with_dirty_mut(|dirty| *dirty = true);
}
/// Removes aliases from a directory
pub fn remove_alias(
&mut self,
path: impl AsRef<str>,
aliases: impl Iterator<Item = impl AsRef<str>>,
) -> bool {
let res = self.with_dirs_mut(|dirs| {
match dirs.iter_mut().find(|dir| dir.path == path.as_ref()) {
Some(dir) => {
let mut res = false;
aliases.for_each(|alias| {
res |= dir.aliases.remove(alias.as_ref());
});
res
}
None => false,
}
});
self.with_dirty_mut(|dirty| *dirty |= res);
res
}
pub fn age(&mut self, max_age: Rank) {
let mut dirty = false;
self.with_dirs_mut(|dirs| {
@ -173,7 +245,7 @@ impl Database {
pub fn sort_by_score(&mut self, now: Epoch) {
self.with_dirs_mut(|dirs| {
dirs.sort_unstable_by(|dir1: &Dir, dir2: &Dir| {
dirs.sort_unstable_by(|dir1: &DirV4, dir2: &DirV4| {
dir1.score(now).total_cmp(&dir2.score(now))
})
});
@ -184,11 +256,11 @@ impl Database {
*self.borrow_dirty()
}
pub fn dirs(&self) -> &[Dir<'_>] {
pub fn dirs(&self) -> &[DirV4<'_>] {
self.borrow_dirs()
}
fn serialize(dirs: &[Dir<'_>]) -> Result<Vec<u8>> {
fn serialize(dirs: &[DirV4<'_>]) -> Result<Vec<u8>> {
(|| -> bincode::Result<_> {
// Preallocate buffer with combined size of sections.
let buffer_size =
@ -204,7 +276,7 @@ impl Database {
.context("could not serialize database")
}
fn deserialize(bytes: &[u8]) -> Result<Vec<Dir<'_>>> {
fn deserialize(bytes: &[u8]) -> Result<Vec<DirV4<'_>>> {
// Assume a maximum size for the database. This prevents bincode from throwing
// strange errors when it encounters invalid data.
const MAX_SIZE: u64 = 32 << 20; // 32 MiB
@ -223,8 +295,27 @@ impl Database {
Self::VERSION => {
deserializer.deserialize(bytes_dirs).context("could not deserialize database")?
}
Self::PREV_VERSION => {
let old_dirs = deserializer
.deserialize::<Vec<DirV3>>(bytes_dirs)
.context("could not deserialize v3 database")?;
old_dirs
.into_iter()
.map(|dir: DirV3| DirV4 {
path: dir.path,
rank: dir.rank,
last_accessed: dir.last_accessed,
aliases: HashSet::new(),
})
.collect()
}
version => {
bail!("unsupported version (got {version}, supports {})", Self::VERSION)
bail!(
"unsupported version (got {version}, supports {}, {})",
Self::VERSION,
Self::PREV_VERSION
)
}
};
@ -260,6 +351,33 @@ mod tests {
}
}
#[test]
fn add_alias() {
let data_dir = tempfile::tempdir().unwrap();
let path = if cfg!(windows) { r"C:\foo\bar" } else { "/foo/bar" };
let now = 946684800;
{
let mut db = Database::open_dir(data_dir.path()).unwrap();
db.add_alias_update(path, ["bar", "fb"].into_iter(), now);
db.add_alias_update(path, ["foobar"].into_iter(), now);
db.save().unwrap();
}
{
let db = Database::open_dir(data_dir.path()).unwrap();
assert_eq!(db.dirs().len(), 1);
let mut aliases = HashSet::from(["bar", "fb", "foobar"]);
let dir = &db.dirs()[0];
assert_eq!(dir.path, path);
assert!(
dir.aliases().all(|alias| aliases.remove(alias.as_ref())) && aliases.is_empty()
);
assert_eq!(dir.last_accessed, now);
}
}
#[test]
fn remove() {
let data_dir = tempfile::tempdir().unwrap();
@ -285,4 +403,34 @@ mod tests {
db.save().unwrap();
}
}
#[test]
fn remove_alias() {
let data_dir = tempfile::tempdir().unwrap();
let path = if cfg!(windows) { r"C:\foo\bar" } else { "/foo/bar" };
let now = 946684800;
{
let mut db = Database::open_dir(data_dir.path()).unwrap();
db.add_alias_update(path, ["fb", "bar", "foobar"].into_iter(), now);
db.save().unwrap();
}
{
let mut db = Database::open_dir(data_dir.path()).unwrap();
assert!(db.remove_alias(path, ["bar", "foobar"].into_iter()));
db.save().unwrap();
}
{
let mut db = Database::open_dir(data_dir.path()).unwrap();
let mut aliases = HashSet::from(["fb"]);
assert_eq!(db.dirs().len(), 1);
assert!(
db.dirs()[0].aliases().all(|alias| aliases.remove(alias.as_ref()))
&& aliases.is_empty()
);
db.save().unwrap();
}
}
}

View File

@ -1,3 +1,5 @@
use std::borrow::Cow;
use std::collections::HashSet;
use std::iter::Rev;
use std::ops::Range;
use std::path::Path;
@ -5,7 +7,7 @@ use std::{fs, path};
use glob::Pattern;
use crate::db::{Database, Dir, Epoch};
use crate::db::{Database, DirV4, Epoch};
use crate::util::{self, MONTH};
pub struct Stream<'a> {
@ -21,29 +23,32 @@ impl<'a> Stream<'a> {
Stream { db, idxs, options }
}
pub fn next(&mut self) -> Option<&Dir<'_>> {
pub fn next(&mut self) -> Option<&DirV4<'_>> {
while let Some(idx) = self.idxs.next() {
let dir = &self.db.dirs()[idx];
if !self.filter_by_keywords(&dir.path) {
continue;
}
if !self.filter_by_base_dir(&dir.path) {
continue;
}
if !self.filter_by_exclude(&dir.path) {
self.db.swap_remove(idx);
continue;
}
// Exists queries are slow, this should always be checked last.
if !self.filter_by_exists(&dir.path) {
if dir.last_accessed < self.options.ttl {
self.db.swap_remove(idx);
// Return dir if any keyword is an alias
if !self.match_aliases(&dir.aliases) {
if !self.filter_by_keywords(&dir.path) {
continue;
}
if !self.filter_by_base_dir(&dir.path) {
continue;
}
if !self.filter_by_exclude(&dir.path) {
self.db.swap_remove(idx);
continue;
}
// Exists queries are slow, this should always be checked last.
if !self.filter_by_exists(&dir.path) {
if dir.last_accessed < self.options.ttl {
self.db.swap_remove(idx);
}
continue;
}
continue;
}
let dir = &self.db.dirs()[idx];
@ -104,6 +109,16 @@ impl<'a> Stream<'a> {
true
}
fn match_aliases(&self, aliases: &HashSet<Cow<'a, str>>) -> bool {
for keyword in &self.options.keywords {
if aliases.contains(keyword.as_str()) {
return true;
}
}
false
}
}
pub struct StreamOptions {
@ -202,10 +217,32 @@ mod tests {
#[case(&["foo", "o", "bar"], "/foo/bar", false)]
#[case(&["/foo/", "/bar"], "/foo/bar", false)]
#[case(&["/foo/", "/bar"], "/foo/baz/bar", true)]
// Aliases
// Case normalization
#[case(&["fOo", "BaR"], "ALIASES=foo,bar", true)]
#[case(&["foo", "BaR"], "ALIASES=foo,bar", true)]
// Exact matches
#[case(&["fo", "ar"], "ALIASES=foo,bar", false)]
#[case(&["foo", "bar"], "ALIASES=foo,bar", true)]
// Mixed aliases and paths
#[case(&["/foo/", "bar", "/baz"], "ALIASES=foo,bar", true)]
fn query(#[case] keywords: &[&str], #[case] path: &str, #[case] is_match: bool) {
let db = &mut Database::new(PathBuf::new(), Vec::new(), |_| Vec::new(), false);
let options = StreamOptions::new(0).with_keywords(keywords.iter());
let stream = Stream::new(db, options);
assert_eq!(is_match, stream.filter_by_keywords(path));
assert_eq!(
is_match,
if path.starts_with("ALIASES=") {
stream.match_aliases(
&path
.trim_start_matches("ALIASES=")
.split(",")
.map(Cow::Borrowed)
.collect::<HashSet<Cow<'_, str>>>(),
)
} else {
stream.filter_by_keywords(path)
}
);
}
}

View File

@ -18,7 +18,7 @@ use std::path::PathBuf;
use anyhow::Result;
use crate::config;
use crate::db::{Database, Dir};
use crate::db::{Database, DirV4};
pub(crate) trait Importer {
/// Yields directory entries to be imported.
@ -26,7 +26,7 @@ pub(crate) trait Importer {
/// The outer `Result` reports failure to fetch the input (e.g. missing
/// file, subprocess errored). The per-item `Result` reports a malformed
/// row, which doesn't necessarily abort the whole import.
fn dirs(&self) -> Result<impl Iterator<Item = Result<Dir<'static>, ImportError>>>;
fn dirs(&self) -> Result<impl Iterator<Item = Result<DirV4<'static>, ImportError>>>;
}
/// A single record that failed to import.

View File

@ -1,18 +1,19 @@
use std::borrow::Cow;
use std::collections::HashSet;
use std::io::{BufRead, BufReader};
use std::process::{Child, ChildStdout, Command, Stdio};
use std::str;
use anyhow::{Context, Result, anyhow};
use crate::db::{Dir, Epoch};
use crate::db::{DirV4, Epoch};
use crate::import::{ImportError, Importer};
#[derive(clap::Args, Clone, Debug)]
pub(crate) struct Atuin {}
impl Importer for Atuin {
fn dirs(&self) -> Result<impl Iterator<Item = Result<Dir<'static>, ImportError>>> {
fn dirs(&self) -> Result<impl Iterator<Item = Result<DirV4<'static>, ImportError>>> {
// atuin renders `{time}` as `YYYY-MM-DD HH:MM:SS` in UTC.
let mut child = Command::new("atuin")
.args(["history", "list", "--format={time}\t{directory}", "--print0"])
@ -46,7 +47,7 @@ impl Iter {
ImportError { path: None, line_num: self.line_num, source }
}
fn parse_line(&self, line: &[u8]) -> Result<Dir<'static>, ImportError> {
fn parse_line(&self, line: &[u8]) -> Result<DirV4<'static>, ImportError> {
let line =
str::from_utf8(line).map_err(|e| self.err(anyhow!(e).context("invalid utf-8")))?;
@ -60,17 +61,18 @@ impl Iter {
.assume_utc()
.unix_timestamp();
let dir = Dir {
let dir = DirV4 {
path: Cow::Owned(path.to_string()),
rank: 1.0,
last_accessed: timestamp as Epoch,
aliases: HashSet::new(),
};
Ok(dir)
}
}
impl Iterator for Iter {
type Item = Result<Dir<'static>, ImportError>;
type Item = Result<DirV4<'static>, ImportError>;
fn next(&mut self) -> Option<Self::Item> {
loop {

View File

@ -1,4 +1,5 @@
use std::borrow::Cow;
use std::collections::HashSet;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::PathBuf;
@ -6,14 +7,14 @@ use std::{env, str};
use anyhow::{Context, Result, anyhow};
use crate::db::Dir;
use crate::db::DirV4;
use crate::import::{ImportError, Importer};
#[derive(clap::Args, Clone, Debug)]
pub(crate) struct Autojump {}
impl Importer for Autojump {
fn dirs(&self) -> Result<impl Iterator<Item = Result<Dir<'static>, ImportError>>> {
fn dirs(&self) -> Result<impl Iterator<Item = Result<DirV4<'static>, ImportError>>> {
let path = data_path()?;
let file = File::open(&path).with_context(|| format!("could not read {path:?}"))?;
let reader = BufReader::new(file);
@ -37,7 +38,7 @@ impl<R: BufRead> Iter<R> {
ImportError { path: Some(self.path.clone()), line_num: self.line_num, source }
}
fn parse_line(&self, line: &[u8]) -> Result<Dir<'static>, ImportError> {
fn parse_line(&self, line: &[u8]) -> Result<DirV4<'static>, ImportError> {
let line =
str::from_utf8(line).map_err(|e| self.err(anyhow!(e).context("invalid utf-8")))?;
@ -52,12 +53,17 @@ impl<R: BufRead> Iter<R> {
// take a while to normalize.
let rank = sigmoid(rank);
Ok(Dir { path: Cow::Owned(path.to_string()), rank, last_accessed: 0 })
Ok(DirV4 {
path: Cow::Owned(path.to_string()),
rank,
last_accessed: 0,
aliases: HashSet::new(),
})
}
}
impl<R: BufRead> Iterator for Iter<R> {
type Item = Result<Dir<'static>, ImportError>;
type Item = Result<DirV4<'static>, ImportError>;
fn next(&mut self) -> Option<Self::Item> {
loop {

View File

@ -5,14 +5,14 @@ use std::path::PathBuf;
use anyhow::{Context, Result};
use crate::db::Dir;
use crate::db::DirV4;
use crate::import::{ImportError, Importer, z};
#[derive(clap::Args, Clone, Debug)]
pub(crate) struct Fasd {}
impl Importer for Fasd {
fn dirs(&self) -> Result<impl Iterator<Item = Result<Dir<'static>, ImportError>>> {
fn dirs(&self) -> Result<impl Iterator<Item = Result<DirV4<'static>, ImportError>>> {
let path = data_path()?;
let file = File::open(&path).with_context(|| format!("could not read {path:?}"))?;
let reader = BufReader::new(file);

View File

@ -1,4 +1,5 @@
use std::borrow::Cow;
use std::collections::HashSet;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::PathBuf;
@ -6,14 +7,14 @@ use std::{env, str};
use anyhow::{Context, Result, anyhow};
use crate::db::Dir;
use crate::db::DirV4;
use crate::import::{ImportError, Importer};
#[derive(clap::Args, Clone, Debug)]
pub(crate) struct Z {}
impl Importer for Z {
fn dirs(&self) -> Result<impl Iterator<Item = Result<Dir<'static>, ImportError>>> {
fn dirs(&self) -> Result<impl Iterator<Item = Result<DirV4<'static>, ImportError>>> {
let path = data_path()?;
let file = File::open(&path).with_context(|| format!("could not read {path:?}"))?;
let reader = BufReader::new(file);
@ -37,7 +38,7 @@ impl<R: BufRead> Iter<R> {
ImportError { path: Some(self.path.clone()), line_num: self.line_num, source }
}
fn parse_line(&self, line: &[u8]) -> Result<Dir<'static>, ImportError> {
fn parse_line(&self, line: &[u8]) -> Result<DirV4<'static>, ImportError> {
let line =
str::from_utf8(line).map_err(|e| self.err(anyhow!(e).context("invalid utf-8")))?;
let err = || self.err(anyhow!("invalid entry: {line}"));
@ -54,12 +55,17 @@ impl<R: BufRead> Iter<R> {
let path = split.next().ok_or_else(err)?;
Ok(Dir { path: Cow::Owned(path.to_string()), rank, last_accessed })
Ok(DirV4 {
path: Cow::Owned(path.to_string()),
rank,
last_accessed,
aliases: HashSet::new(),
})
}
}
impl<R: BufRead> Iterator for Iter<R> {
type Item = Result<Dir<'static>, ImportError>;
type Item = Result<DirV4<'static>, ImportError>;
fn next(&mut self) -> Option<Self::Item> {
loop {

View File

@ -6,14 +6,14 @@ use std::path::PathBuf;
use anyhow::{Context, Result};
use crate::db::Dir;
use crate::db::DirV4;
use crate::import::{ImportError, Importer, z};
#[derive(clap::Args, Clone, Debug)]
pub(crate) struct ZLua {}
impl Importer for ZLua {
fn dirs(&self) -> Result<impl Iterator<Item = Result<Dir<'static>, ImportError>>> {
fn dirs(&self) -> Result<impl Iterator<Item = Result<DirV4<'static>, ImportError>>> {
let path = data_path()?;
let err = match File::open(&path) {
Ok(file) => return Ok(z::Iter::new(BufReader::new(file), path)),

View File

@ -5,14 +5,14 @@ use std::path::PathBuf;
use anyhow::{Context, Result};
use crate::db::Dir;
use crate::db::DirV4;
use crate::import::{ImportError, Importer, z};
#[derive(clap::Args, Clone, Debug)]
pub(crate) struct ZshZ {}
impl Importer for ZshZ {
fn dirs(&self) -> Result<impl Iterator<Item = Result<Dir<'static>, ImportError>>> {
fn dirs(&self) -> Result<impl Iterator<Item = Result<DirV4<'static>, ImportError>>> {
let path = data_path()?;
let file = File::open(&path).with_context(|| format!("could not read {path:?}"))?;
let reader = BufReader::new(file);

View File

@ -10,7 +10,7 @@ use std::{env, mem};
use anyhow::anyhow;
use anyhow::{Context, Result, bail};
use crate::db::{Dir, Epoch};
use crate::db::{DirV4, Epoch};
use crate::error::SilentExit;
pub const SECOND: Epoch = 1;
@ -41,7 +41,7 @@ impl Fzf {
cmd.args([
// Search mode
"--delimiter=\t",
"--nth=2",
"--nth=2,3",
// Scripting
"--read0",
])
@ -60,9 +60,9 @@ impl Fzf {
self.args([
// Non-POSIX args are only available on certain operating systems.
if cfg!(target_os = "linux") {
r"--preview=\command -p ls -Cp --color=always --group-directories-first {2..}"
r"--preview=\command -p ls -Cp --color=always --group-directories-first {3..}"
} else {
r"--preview=\command -p ls -Cp {2..}"
r"--preview=\command -p ls -Cp {3..}"
},
// Rounded edges don't display correctly on some terminals.
"--preview-window=down,30%,sharp",
@ -121,9 +121,13 @@ impl Fzf {
pub struct FzfChild(Child);
impl FzfChild {
pub fn write(&mut self, dir: &Dir, now: Epoch) -> Result<Option<String>> {
pub fn write(&mut self, dir: &DirV4, now: Epoch, aliases: bool) -> Result<Option<String>> {
let handle = self.0.stdin.as_mut().unwrap();
match write!(handle, "{}\0", dir.display().with_score(now).with_separator('\t')) {
match write!(
handle,
"{}\0",
dir.display().with_score(now).with_aliases(aliases).with_separator('\t')
) {
Ok(()) => Ok(None),
Err(e) if e.kind() == io::ErrorKind::BrokenPipe => self.wait().map(Some),
Err(e) => Err(e).context("could not write to fzf"),