Merge f7d92b7a98
into 194f8e31e4
This commit is contained in:
commit
88c2259f96
|
@ -144,6 +144,16 @@ _arguments "${_arguments_options[@]}" : \
|
||||||
'--version[Print version]' \
|
'--version[Print version]' \
|
||||||
'*::paths:_files -/' \
|
'*::paths:_files -/' \
|
||||||
&& ret=0
|
&& ret=0
|
||||||
|
;;
|
||||||
|
(bookmark)
|
||||||
|
_arguments "${_arguments_options[@]}" : \
|
||||||
|
'-h[Print help]' \
|
||||||
|
'--help[Print help]' \
|
||||||
|
'-V[Print version]' \
|
||||||
|
'--version[Print version]' \
|
||||||
|
':bookmark_id:_default' \
|
||||||
|
':path:_files -/' \
|
||||||
|
&& ret=0
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
;;
|
;;
|
||||||
|
@ -159,6 +169,7 @@ _zoxide_commands() {
|
||||||
'init:Generate shell configuration' \
|
'init:Generate shell configuration' \
|
||||||
'query:Search for a directory in the database' \
|
'query:Search for a directory in the database' \
|
||||||
'remove:Remove a directory from the database' \
|
'remove:Remove a directory from the database' \
|
||||||
|
'bookmark:' \
|
||||||
)
|
)
|
||||||
_describe -t commands 'zoxide commands' commands "$@"
|
_describe -t commands 'zoxide commands' commands "$@"
|
||||||
}
|
}
|
||||||
|
@ -167,6 +178,11 @@ _zoxide__add_commands() {
|
||||||
local commands; commands=()
|
local commands; commands=()
|
||||||
_describe -t commands 'zoxide add commands' commands "$@"
|
_describe -t commands 'zoxide add commands' commands "$@"
|
||||||
}
|
}
|
||||||
|
(( $+functions[_zoxide__bookmark_commands] )) ||
|
||||||
|
_zoxide__bookmark_commands() {
|
||||||
|
local commands; commands=()
|
||||||
|
_describe -t commands 'zoxide bookmark commands' commands "$@"
|
||||||
|
}
|
||||||
(( $+functions[_zoxide__edit_commands] )) ||
|
(( $+functions[_zoxide__edit_commands] )) ||
|
||||||
_zoxide__edit_commands() {
|
_zoxide__edit_commands() {
|
||||||
local commands; commands=(
|
local commands; commands=(
|
||||||
|
|
|
@ -31,6 +31,7 @@ Register-ArgumentCompleter -Native -CommandName 'zoxide' -ScriptBlock {
|
||||||
[CompletionResult]::new('init', 'init', [CompletionResultType]::ParameterValue, 'Generate shell configuration')
|
[CompletionResult]::new('init', 'init', [CompletionResultType]::ParameterValue, 'Generate shell configuration')
|
||||||
[CompletionResult]::new('query', 'query', [CompletionResultType]::ParameterValue, 'Search for a directory in the database')
|
[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', 'remove', [CompletionResultType]::ParameterValue, 'Remove a directory from the database')
|
||||||
|
[CompletionResult]::new('bookmark', 'bookmark', [CompletionResultType]::ParameterValue, 'bookmark')
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
'zoxide;add' {
|
'zoxide;add' {
|
||||||
|
@ -124,6 +125,13 @@ Register-ArgumentCompleter -Native -CommandName 'zoxide' -ScriptBlock {
|
||||||
[CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version')
|
[CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version')
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
'zoxide;bookmark' {
|
||||||
|
[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*" } |
|
$completions.Where{ $_.CompletionText -like "$wordToComplete*" } |
|
||||||
|
|
|
@ -19,6 +19,9 @@ _zoxide() {
|
||||||
zoxide,add)
|
zoxide,add)
|
||||||
cmd="zoxide__add"
|
cmd="zoxide__add"
|
||||||
;;
|
;;
|
||||||
|
zoxide,bookmark)
|
||||||
|
cmd="zoxide__bookmark"
|
||||||
|
;;
|
||||||
zoxide,edit)
|
zoxide,edit)
|
||||||
cmd="zoxide__edit"
|
cmd="zoxide__edit"
|
||||||
;;
|
;;
|
||||||
|
@ -53,7 +56,7 @@ _zoxide() {
|
||||||
|
|
||||||
case "${cmd}" in
|
case "${cmd}" in
|
||||||
zoxide)
|
zoxide)
|
||||||
opts="-h -V --help --version add edit import init query remove"
|
opts="-h -V --help --version add edit import init query remove bookmark"
|
||||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
|
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
|
||||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||||
return 0
|
return 0
|
||||||
|
@ -88,6 +91,20 @@ _zoxide() {
|
||||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||||
return 0
|
return 0
|
||||||
;;
|
;;
|
||||||
|
zoxide__bookmark)
|
||||||
|
opts="-h -V --help --version <BOOKMARK_ID> <PATH>"
|
||||||
|
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
|
||||||
|
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
case "${prev}" in
|
||||||
|
*)
|
||||||
|
COMPREPLY=()
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
zoxide__edit)
|
zoxide__edit)
|
||||||
opts="-h -V --help --version decrement delete increment reload"
|
opts="-h -V --help --version decrement delete increment reload"
|
||||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
|
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
|
||||||
|
|
|
@ -28,6 +28,7 @@ set edit:completion:arg-completer[zoxide] = {|@words|
|
||||||
cand init 'Generate shell configuration'
|
cand init 'Generate shell configuration'
|
||||||
cand query 'Search for a directory in the database'
|
cand query 'Search for a directory in the database'
|
||||||
cand remove 'Remove a directory from the database'
|
cand remove 'Remove a directory from the database'
|
||||||
|
cand bookmark 'bookmark'
|
||||||
}
|
}
|
||||||
&'zoxide;add'= {
|
&'zoxide;add'= {
|
||||||
cand -s 'The rank to increment the entry if it exists or initialize it with if it doesn''t'
|
cand -s 'The rank to increment the entry if it exists or initialize it with if it doesn''t'
|
||||||
|
@ -110,6 +111,12 @@ set edit:completion:arg-completer[zoxide] = {|@words|
|
||||||
cand -V 'Print version'
|
cand -V 'Print version'
|
||||||
cand --version 'Print version'
|
cand --version 'Print version'
|
||||||
}
|
}
|
||||||
|
&'zoxide;bookmark'= {
|
||||||
|
cand -h 'Print help'
|
||||||
|
cand --help 'Print help'
|
||||||
|
cand -V 'Print version'
|
||||||
|
cand --version 'Print version'
|
||||||
|
}
|
||||||
]
|
]
|
||||||
$completions[$command]
|
$completions[$command]
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ complete -c zoxide -n "__fish_zoxide_needs_command" -f -a "import" -d 'Import en
|
||||||
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 "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 "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" -d 'Remove a directory from the database'
|
||||||
|
complete -c zoxide -n "__fish_zoxide_needs_command" -f -a "bookmark"
|
||||||
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 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 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" -s V -l version -d 'Print version'
|
||||||
|
@ -71,3 +72,5 @@ complete -c zoxide -n "__fish_zoxide_using_subcommand query" -s h -l help -d 'Pr
|
||||||
complete -c zoxide -n "__fish_zoxide_using_subcommand query" -s V -l version -d 'Print version'
|
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 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" -s V -l version -d 'Print version'
|
||||||
|
complete -c zoxide -n "__fish_zoxide_using_subcommand bookmark" -s h -l help -d 'Print help'
|
||||||
|
complete -c zoxide -n "__fish_zoxide_using_subcommand bookmark" -s V -l version -d 'Print version'
|
||||||
|
|
|
@ -94,6 +94,13 @@ module completions {
|
||||||
--version(-V) # Print version
|
--version(-V) # Print version
|
||||||
]
|
]
|
||||||
|
|
||||||
|
export extern "zoxide bookmark" [
|
||||||
|
bookmark_id: string
|
||||||
|
path: path
|
||||||
|
--help(-h) # Print help
|
||||||
|
--version(-V) # Print version
|
||||||
|
]
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export use completions *
|
export use completions *
|
||||||
|
|
|
@ -283,6 +283,28 @@ const completion: Fig.Spec = {
|
||||||
template: "folders",
|
template: "folders",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "bookmark",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: ["-h", "--help"],
|
||||||
|
description: "Print help",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: ["-V", "--version"],
|
||||||
|
description: "Print version",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
args: [
|
||||||
|
{
|
||||||
|
name: "bookmark_id",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "path",
|
||||||
|
template: "folders",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
],
|
],
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
use super::{Bookmark, Run};
|
||||||
|
use crate::db::Database;
|
||||||
|
|
||||||
|
impl Run for Bookmark {
|
||||||
|
fn run(&self) -> Result<()> {
|
||||||
|
let mut db = crate::db::Database::open()?;
|
||||||
|
self.add_bookmark(&mut db).and(db.save())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Bookmark {
|
||||||
|
fn add_bookmark(&self, db: &mut Database) -> Result<()> {
|
||||||
|
db.add_bookmark(self.bookmark_id.clone(), self.path.clone());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -47,6 +47,7 @@ pub enum Cmd {
|
||||||
Init(Init),
|
Init(Init),
|
||||||
Query(Query),
|
Query(Query),
|
||||||
Remove(Remove),
|
Remove(Remove),
|
||||||
|
Bookmark(Bookmark),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a new directory or increment its rank
|
/// Add a new directory or increment its rank
|
||||||
|
@ -202,3 +203,11 @@ pub struct Remove {
|
||||||
#[clap(value_hint = ValueHint::DirPath)]
|
#[clap(value_hint = ValueHint::DirPath)]
|
||||||
pub paths: Vec<String>,
|
pub paths: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
#[clap(author, help_template = HelpTemplate)]
|
||||||
|
pub struct Bookmark {
|
||||||
|
pub bookmark_id: String,
|
||||||
|
#[clap(value_hint = ValueHint::DirPath)]
|
||||||
|
pub path: PathBuf,
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
mod add;
|
mod add;
|
||||||
|
mod bookmark;
|
||||||
mod cmd;
|
mod cmd;
|
||||||
mod edit;
|
mod edit;
|
||||||
mod import;
|
mod import;
|
||||||
|
@ -23,6 +24,7 @@ impl Run for Cmd {
|
||||||
Cmd::Init(cmd) => cmd.run(),
|
Cmd::Init(cmd) => cmd.run(),
|
||||||
Cmd::Query(cmd) => cmd.run(),
|
Cmd::Query(cmd) => cmd.run(),
|
||||||
Cmd::Remove(cmd) => cmd.run(),
|
Cmd::Remove(cmd) => cmd.run(),
|
||||||
|
Cmd::Bookmark(cmd) => cmd.run(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,13 @@ impl Run for Query {
|
||||||
impl Query {
|
impl Query {
|
||||||
fn query(&self, db: &mut Database) -> Result<()> {
|
fn query(&self, db: &mut Database) -> Result<()> {
|
||||||
let now = util::current_time()?;
|
let now = util::current_time()?;
|
||||||
|
|
||||||
|
if let Ok(is_mark) = self.try_bookmark(db) {
|
||||||
|
if is_mark {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut stream = self.get_stream(db, now)?;
|
let mut stream = self.get_stream(db, now)?;
|
||||||
|
|
||||||
if self.interactive {
|
if self.interactive {
|
||||||
|
@ -28,7 +35,22 @@ impl Query {
|
||||||
self.query_first(&mut stream, now)
|
self.query_first(&mut stream, now)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fn try_bookmark(&self, db: &Database) -> Result<bool, ()> {
|
||||||
|
// NOTE We only assume bookmarking if they supply one keyword
|
||||||
|
// Could be trivially changed to iterate over keywords
|
||||||
|
if self.keywords.len() == 1 {
|
||||||
|
let keyword = &self.keywords[0];
|
||||||
|
if let Some(path) = db.get_bookmark(keyword) {
|
||||||
|
let handle = &mut io::stdout();
|
||||||
|
return match writeln!(handle, "{}", path.to_str().unwrap()).pipe_exit("stdout") {
|
||||||
|
Ok(_) => Ok(true),
|
||||||
|
Err(_) => Err(()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
fn query_interactive(&self, stream: &mut Stream, now: Epoch) -> Result<()> {
|
fn query_interactive(&self, stream: &mut Stream, now: Epoch) -> Result<()> {
|
||||||
let mut fzf = Self::get_fzf()?;
|
let mut fzf = Self::get_fzf()?;
|
||||||
let selection = loop {
|
let selection = loop {
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
mod dir;
|
mod dir;
|
||||||
mod stream;
|
mod stream;
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::{fs, io};
|
use std::{fs, io};
|
||||||
|
|
||||||
use anyhow::{Context, Result, bail};
|
use anyhow::{Context, Result, bail};
|
||||||
use bincode::Options;
|
use bincode::{Options, serialize};
|
||||||
use ouroboros::self_referencing;
|
use ouroboros::self_referencing;
|
||||||
|
|
||||||
pub use crate::db::dir::{Dir, Epoch, Rank};
|
pub use crate::db::dir::{Dir, Epoch, Rank};
|
||||||
|
@ -18,7 +19,11 @@ pub struct Database {
|
||||||
bytes: Vec<u8>,
|
bytes: Vec<u8>,
|
||||||
#[borrows(bytes)]
|
#[borrows(bytes)]
|
||||||
#[covariant]
|
#[covariant]
|
||||||
pub dirs: Vec<Dir<'this>>,
|
// NOTE Directories and Bookmarks
|
||||||
|
// They must be the same field otherwise two closures which take bytes and yield each
|
||||||
|
// respectively would have to be constructed, causing bytes requiring bytes to have static
|
||||||
|
// lifetime
|
||||||
|
pub dirs: (Vec<Dir<'this>>, HashMap<String, PathBuf>),
|
||||||
dirty: bool,
|
dirty: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,14 +41,16 @@ impl Database {
|
||||||
let path = fs::canonicalize(&path).unwrap_or(path);
|
let path = fs::canonicalize(&path).unwrap_or(path);
|
||||||
|
|
||||||
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.clone(), bytes, |bytes| Self::deserialize(bytes, path), 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
|
// Create data directory, but don't create any file yet. The file will be
|
||||||
// created 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).with_context(|| {
|
fs::create_dir_all(data_dir).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(), HashMap::new()), false))
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
Err(e).with_context(|| format!("could not read from database: {}", path.display()))
|
Err(e).with_context(|| format!("could not read from database: {}", path.display()))
|
||||||
|
@ -57,7 +64,7 @@ impl Database {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let bytes = Self::serialize(self.dirs())?;
|
let bytes = Self::serialize((self.dirs(), self.bookmarks()))?;
|
||||||
util::write(self.borrow_path(), bytes).context("could not write to database")?;
|
util::write(self.borrow_path(), bytes).context("could not write to database")?;
|
||||||
self.with_dirty_mut(|dirty| *dirty = false);
|
self.with_dirty_mut(|dirty| *dirty = false);
|
||||||
|
|
||||||
|
@ -66,10 +73,10 @@ impl Database {
|
||||||
|
|
||||||
/// Increments the rank of a directory, or creates it if it does not exist.
|
/// 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) {
|
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.0.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 => {
|
None => {
|
||||||
dirs.push(Dir { path: path.into().into(), rank: by.max(0.0), last_accessed: now })
|
dirs.0.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);
|
||||||
|
@ -80,7 +87,7 @@ impl Database {
|
||||||
/// 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| {
|
self.with_dirs_mut(|dirs| {
|
||||||
dirs.push(Dir { path: path.into().into(), rank, last_accessed: now })
|
dirs.0.push(Dir { path: path.into().into(), rank, last_accessed: now })
|
||||||
});
|
});
|
||||||
self.with_dirty_mut(|dirty| *dirty = true);
|
self.with_dirty_mut(|dirty| *dirty = true);
|
||||||
}
|
}
|
||||||
|
@ -88,13 +95,13 @@ impl Database {
|
||||||
/// Increments the rank and updates the last_accessed of a directory, or
|
/// Increments the rank and updates the last_accessed of a directory, or
|
||||||
/// creates it if it does not exist.
|
/// creates it if it does not exist.
|
||||||
pub fn add_update(&mut self, path: impl AsRef<str> + Into<String>, by: Rank, now: Epoch) {
|
pub fn add_update(&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.0.iter_mut().find(|dir| dir.path == path.as_ref()) {
|
||||||
Some(dir) => {
|
Some(dir) => {
|
||||||
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 => {
|
None => {
|
||||||
dirs.push(Dir { path: path.into().into(), rank: by.max(0.0), last_accessed: now })
|
dirs.0.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);
|
||||||
|
@ -113,21 +120,21 @@ impl Database {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn swap_remove(&mut self, idx: usize) {
|
pub fn swap_remove(&mut self, idx: usize) {
|
||||||
self.with_dirs_mut(|dirs| dirs.swap_remove(idx));
|
self.with_dirs_mut(|dirs| dirs.0.swap_remove(idx));
|
||||||
self.with_dirty_mut(|dirty| *dirty = true);
|
self.with_dirty_mut(|dirty| *dirty = true);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn age(&mut self, max_age: Rank) {
|
pub fn age(&mut self, max_age: Rank) {
|
||||||
let mut dirty = false;
|
let mut dirty = false;
|
||||||
self.with_dirs_mut(|dirs| {
|
self.with_dirs_mut(|dirs| {
|
||||||
let total_age = dirs.iter().map(|dir| dir.rank).sum::<Rank>();
|
let total_age = dirs.0.iter().map(|dir| dir.rank).sum::<Rank>();
|
||||||
if total_age > max_age {
|
if total_age > max_age {
|
||||||
let factor = 0.9 * max_age / total_age;
|
let factor = 0.9 * max_age / total_age;
|
||||||
for idx in (0..dirs.len()).rev() {
|
for idx in (0..dirs.0.len()).rev() {
|
||||||
let dir = &mut dirs[idx];
|
let dir = &mut dirs.0[idx];
|
||||||
dir.rank *= factor;
|
dir.rank *= factor;
|
||||||
if dir.rank < 1.0 {
|
if dir.rank < 1.0 {
|
||||||
dirs.swap_remove(idx);
|
dirs.0.swap_remove(idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dirty = true;
|
dirty = true;
|
||||||
|
@ -142,10 +149,10 @@ impl Database {
|
||||||
|
|
||||||
let mut dirty = false;
|
let mut dirty = false;
|
||||||
self.with_dirs_mut(|dirs| {
|
self.with_dirs_mut(|dirs| {
|
||||||
for idx in (1..dirs.len()).rev() {
|
for idx in (1..dirs.0.len()).rev() {
|
||||||
// Check if curr_dir and next_dir have equal paths.
|
// Check if curr_dir and next_dir have equal paths.
|
||||||
let curr_dir = &dirs[idx];
|
let curr_dir = &dirs.0[idx];
|
||||||
let next_dir = &dirs[idx - 1];
|
let next_dir = &dirs.0[idx - 1];
|
||||||
if next_dir.path != curr_dir.path {
|
if next_dir.path != curr_dir.path {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -153,12 +160,12 @@ impl Database {
|
||||||
// Merge curr_dir's rank and last_accessed into next_dir.
|
// Merge curr_dir's rank and last_accessed into next_dir.
|
||||||
let rank = curr_dir.rank;
|
let rank = curr_dir.rank;
|
||||||
let last_accessed = curr_dir.last_accessed;
|
let last_accessed = curr_dir.last_accessed;
|
||||||
let next_dir = &mut dirs[idx - 1];
|
let next_dir = &mut dirs.0[idx - 1];
|
||||||
next_dir.last_accessed = next_dir.last_accessed.max(last_accessed);
|
next_dir.last_accessed = next_dir.last_accessed.max(last_accessed);
|
||||||
next_dir.rank += rank;
|
next_dir.rank += rank;
|
||||||
|
|
||||||
// Delete curr_dir.
|
// Delete curr_dir.
|
||||||
dirs.swap_remove(idx);
|
dirs.0.swap_remove(idx);
|
||||||
dirty = true;
|
dirty = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -166,13 +173,13 @@ impl Database {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sort_by_path(&mut self) {
|
pub fn sort_by_path(&mut self) {
|
||||||
self.with_dirs_mut(|dirs| dirs.sort_unstable_by(|dir1, dir2| dir1.path.cmp(&dir2.path)));
|
self.with_dirs_mut(|dirs| dirs.0.sort_unstable_by(|dir1, dir2| dir1.path.cmp(&dir2.path)));
|
||||||
self.with_dirty_mut(|dirty| *dirty = true);
|
self.with_dirty_mut(|dirty| *dirty = true);
|
||||||
}
|
}
|
||||||
|
|
||||||
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| {
|
dirs.0.sort_unstable_by(|dir1: &Dir, dir2: &Dir| {
|
||||||
dir1.score(now).total_cmp(&dir2.score(now))
|
dir1.score(now).total_cmp(&dir2.score(now))
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
@ -184,10 +191,23 @@ impl Database {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dirs(&self) -> &[Dir<'_>] {
|
pub fn dirs(&self) -> &[Dir<'_>] {
|
||||||
self.borrow_dirs()
|
&self.borrow_dirs().0
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serialize(dirs: &[Dir<'_>]) -> Result<Vec<u8>> {
|
pub fn bookmarks(&self) -> &HashMap<String, PathBuf> {
|
||||||
|
&self.borrow_dirs().1
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_bookmark(&self, id: &str) -> Option<&PathBuf> {
|
||||||
|
self.borrow_dirs().1.get(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_bookmark(&mut self, id: String, path: PathBuf) {
|
||||||
|
self.with_dirs_mut(|dirs| dirs.1.insert(id, path));
|
||||||
|
self.with_dirty_mut(|is_dirty| *is_dirty = true);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize(dirs: (&[Dir<'_>], &HashMap<String, PathBuf>)) -> Result<Vec<u8>> {
|
||||||
(|| -> bincode::Result<_> {
|
(|| -> bincode::Result<_> {
|
||||||
// Preallocate buffer with combined size of sections.
|
// Preallocate buffer with combined size of sections.
|
||||||
let buffer_size =
|
let buffer_size =
|
||||||
|
@ -203,7 +223,10 @@ impl Database {
|
||||||
.context("could not serialize database")
|
.context("could not serialize database")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deserialize(bytes: &[u8]) -> Result<Vec<Dir<'_>>> {
|
fn deserialize(
|
||||||
|
bytes: &[u8],
|
||||||
|
path: PathBuf,
|
||||||
|
) -> Result<(Vec<Dir<'_>>, HashMap<String, PathBuf>)> {
|
||||||
// Assume a maximum size for the database. This prevents bincode from throwing
|
// Assume a maximum size for the database. This prevents bincode from throwing
|
||||||
// strange 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
|
||||||
|
@ -220,7 +243,23 @@ impl Database {
|
||||||
let version = deserializer.deserialize(bytes_version)?;
|
let version = deserializer.deserialize(bytes_version)?;
|
||||||
let dirs = match version {
|
let dirs = match version {
|
||||||
Self::VERSION => {
|
Self::VERSION => {
|
||||||
deserializer.deserialize(bytes_dirs).context("could not deserialize database")?
|
match deserializer.deserialize::<(Vec<Dir>, HashMap<String, PathBuf>)>(bytes_dirs) {
|
||||||
|
Err(err) => {
|
||||||
|
let dirs: Vec<Dir> = match deserializer.deserialize(bytes_dirs) {
|
||||||
|
Ok(dirs) => dirs,
|
||||||
|
Err(_) => return Err(err).context("could not deserialize database"),
|
||||||
|
};
|
||||||
|
let bookmarks: HashMap<String, PathBuf> = HashMap::new();
|
||||||
|
match serialize(&(&dirs, &bookmarks)) {
|
||||||
|
Ok(_) => {
|
||||||
|
util::write(path, bytes).context("could not write to database")?;
|
||||||
|
return Ok((dirs, bookmarks));
|
||||||
|
}
|
||||||
|
Err(_) => return Err(err).context("could not deserialize database"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Ok(dirs) => dirs,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
version => {
|
version => {
|
||||||
bail!("unsupported version (got {version}, supports {})", Self::VERSION)
|
bail!("unsupported version (got {version}, supports {})", Self::VERSION)
|
||||||
|
@ -268,6 +307,7 @@ mod tests {
|
||||||
{
|
{
|
||||||
let mut db = Database::open_dir(data_dir.path()).unwrap();
|
let mut db = Database::open_dir(data_dir.path()).unwrap();
|
||||||
db.add(path, 1.0, now);
|
db.add(path, 1.0, now);
|
||||||
|
db.add(path, 1.0, now);
|
||||||
db.save().unwrap();
|
db.save().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -176,6 +176,7 @@ impl StreamOptions {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use rstest::rstest;
|
use rstest::rstest;
|
||||||
|
@ -203,7 +204,8 @@ mod tests {
|
||||||
#[case(&["/foo/", "/bar"], "/foo/bar", false)]
|
#[case(&["/foo/", "/bar"], "/foo/bar", false)]
|
||||||
#[case(&["/foo/", "/bar"], "/foo/baz/bar", true)]
|
#[case(&["/foo/", "/bar"], "/foo/baz/bar", true)]
|
||||||
fn query(#[case] keywords: &[&str], #[case] path: &str, #[case] is_match: bool) {
|
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 db =
|
||||||
|
&mut Database::new(PathBuf::new(), Vec::new(), |_| (Vec::new(), HashMap::new()), false);
|
||||||
let options = StreamOptions::new(0).with_keywords(keywords.iter());
|
let options = StreamOptions::new(0).with_keywords(keywords.iter());
|
||||||
let stream = Stream::new(db, options);
|
let stream = Stream::new(db, options);
|
||||||
assert_eq!(is_match, stream.filter_by_keywords(path));
|
assert_eq!(is_match, stream.filter_by_keywords(path));
|
||||||
|
|
Loading…
Reference in New Issue