mirror of
https://github.com/Llewellynvdm/zoxide.git
synced 2024-11-17 02:25:15 +00:00
Add _ZO_RESOLVE_SYMLINKS to resolve or not symlinks (#85)
Fixes #80. Disable by default the symlinks resolution, making a symlink and its target 2 different entries in the database. Adds the _ZO_RESOLVE_SYMLINKS env variable to re-enable it. Example: /tmp/foo-target is a directory /tmp/foo symlinks to /tmp/foo-target With _ZO_RESOLVE_SYMLINKS=1, `z add /tmp/foo` adds `/tmp/foo-target` in the database. With _ZO_RESOLVE_SYMLINKS=0 or unset, `z add /tmp/foo` adds `/tmp/foo` in the database.
This commit is contained in:
parent
c5bc49884d
commit
d49a2c1495
@ -206,5 +206,6 @@ eval "$(zoxide init zsh)"
|
||||
("`:`" on Linux/macOS, "`;`" on Windows) to be excluded from the database
|
||||
- `$_ZO_FZF_OPTS`: custom flags to pass to `fzf`
|
||||
- `$_ZO_MAXAGE`: sets the maximum total age after which entries start getting deleted
|
||||
- `$_ZO_RESOLVE_SYMLINKS`: when set to `1`, `z add` will resolve symlinks.
|
||||
|
||||
[`dirs` documentation]: https://docs.rs/dirs/latest/dirs/fn.data_local_dir.html
|
||||
|
@ -52,3 +52,10 @@ pub fn zo_maxage() -> Result<Rank> {
|
||||
None => Ok(10000.0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn zo_resolve_symlinks() -> bool {
|
||||
match env::var_os("_ZO_RESOLVE_SYMLINKS") {
|
||||
Some(var) => var == "1",
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,9 @@ use crate::config;
|
||||
use crate::db::{Db, Dir, Rank};
|
||||
use crate::util;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use anyhow::Result;
|
||||
use structopt::StructOpt;
|
||||
|
||||
use std::env;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// Add a new directory or increment its rank
|
||||
@ -21,7 +20,7 @@ impl Add {
|
||||
let path = match &self.path {
|
||||
Some(path) => path,
|
||||
None => {
|
||||
current_dir = env::current_dir().context("unable to fetch current directory")?;
|
||||
current_dir = util::get_current_dir()?;
|
||||
¤t_dir
|
||||
}
|
||||
};
|
||||
@ -32,7 +31,11 @@ impl Add {
|
||||
|
||||
fn add<P: AsRef<Path>>(path: P) -> Result<()> {
|
||||
let path = path.as_ref();
|
||||
let path = util::canonicalize(&path)?;
|
||||
let path = if config::zo_resolve_symlinks() {
|
||||
util::canonicalize(&path)?
|
||||
} else {
|
||||
util::resolve_path(&path)?
|
||||
};
|
||||
|
||||
if config::zo_exclude_dirs().contains(&path) {
|
||||
return Ok(());
|
||||
|
@ -1,3 +1,4 @@
|
||||
use crate::config;
|
||||
use crate::db::{Db, Dir};
|
||||
use crate::util;
|
||||
|
||||
@ -39,7 +40,7 @@ fn import<P: AsRef<Path>>(path: P, merge: bool) -> Result<()> {
|
||||
.with_context(|| format!("could not read z database: {}", path.display()))?;
|
||||
|
||||
for (idx, line) in buffer.lines().enumerate() {
|
||||
if let Err(e) = import_line(&mut db, line) {
|
||||
if let Err(e) = import_line(&mut db, line, config::zo_resolve_symlinks()) {
|
||||
let line_num = idx + 1;
|
||||
eprintln!("Error on line {}: {}", line_num, e);
|
||||
}
|
||||
@ -51,7 +52,7 @@ fn import<P: AsRef<Path>>(path: P, merge: bool) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn import_line(db: &mut Db, line: &str) -> Result<()> {
|
||||
fn import_line(db: &mut Db, line: &str, resolve_symlinks: bool) -> Result<()> {
|
||||
let mut split_line = line.rsplitn(3, '|');
|
||||
|
||||
let (path, epoch_str, rank_str) = (|| {
|
||||
@ -70,7 +71,11 @@ fn import_line(db: &mut Db, line: &str) -> Result<()> {
|
||||
.parse::<f64>()
|
||||
.with_context(|| format!("invalid rank: {}", rank_str))?;
|
||||
|
||||
let path = util::canonicalize(&path)?;
|
||||
let path = if resolve_symlinks {
|
||||
util::canonicalize(&path)?
|
||||
} else {
|
||||
util::resolve_path(&path)?
|
||||
};
|
||||
let path = util::path_to_str(&path)?;
|
||||
|
||||
// If the path exists in the database, add the ranks and set the epoch to
|
||||
|
@ -15,7 +15,7 @@ pub const CONFIG: ShellConfig = ShellConfig {
|
||||
|
||||
const HOOK_PROMPT: &str = r#"
|
||||
_zoxide_hook() {
|
||||
zoxide add
|
||||
zoxide add "$(pwd -L)"
|
||||
}
|
||||
|
||||
case "$PROMPT_COMMAND" in
|
||||
@ -31,7 +31,7 @@ _zoxide_hook() {
|
||||
_ZO_PWD="${PWD}"
|
||||
elif [ "${_ZO_PWD}" != "${PWD}" ]; then
|
||||
_ZO_PWD="${PWD}"
|
||||
zoxide add
|
||||
zoxide add "$(pwd -L)"
|
||||
fi
|
||||
}
|
||||
|
||||
|
@ -69,14 +69,14 @@ end
|
||||
|
||||
const HOOK_PROMPT: &str = r#"
|
||||
function _zoxide_hook --on-event fish_prompt
|
||||
zoxide add
|
||||
zoxide add $(pwd -L)
|
||||
end
|
||||
"#;
|
||||
|
||||
const fn hook_pwd() -> Result<Cow<'static, str>> {
|
||||
const HOOK_PWD: &str = r#"
|
||||
function _zoxide_hook --on-variable PWD
|
||||
zoxide add
|
||||
zoxide add "$(pwd -L)"
|
||||
end
|
||||
"#;
|
||||
|
||||
|
@ -68,7 +68,7 @@ alias {0}r='zoxide remove'
|
||||
|
||||
const HOOK_PROMPT: &str = r#"
|
||||
_zoxide_hook() {
|
||||
zoxide add
|
||||
zoxide add "$(pwd -L)"
|
||||
}
|
||||
|
||||
case "$PS1" in
|
||||
@ -117,7 +117,7 @@ _zoxide_setpwd
|
||||
_zoxide_hook() {{
|
||||
_ZO_OLDPWD="$(cat "$_ZO_PWD_PATH")"
|
||||
if [ -z "$_ZO_OLDPWD" ] || [ "$_ZO_OLDPWD" != "$PWD" ]; then
|
||||
_zoxide_setpwd && zoxide add > /dev/null
|
||||
_zoxide_setpwd && zoxide add "$(pwd -L)" > /dev/null
|
||||
fi
|
||||
}}
|
||||
|
||||
|
@ -72,7 +72,7 @@ function {0}ri {{
|
||||
const HOOK_PROMPT: &str = r#"
|
||||
$PreZoxidePrompt = $function:prompt
|
||||
function prompt {
|
||||
$null = zoxide add
|
||||
$null = zoxide add $(Get-Location)
|
||||
& $PreZoxidePrompt
|
||||
}
|
||||
"#;
|
||||
@ -81,7 +81,7 @@ const fn hook_pwd() -> Result<Cow<'static, str>> {
|
||||
const HOOK_PWD: &str = r#"
|
||||
if ($PSVersionTable.PSVersion.Major -ge 6) {
|
||||
$ExecutionContext.InvokeCommand.LocationChangedAction = {
|
||||
$null = zoxide add
|
||||
$null = zoxide add $(Get-Location)
|
||||
}
|
||||
} else {
|
||||
Write-Error "pwd hook requires pwsh - use 'zoxide init powershell --hook prompt'"
|
||||
|
@ -15,7 +15,7 @@ pub const CONFIG: ShellConfig = ShellConfig {
|
||||
|
||||
const HOOK_PROMPT: &str = r#"
|
||||
_zoxide_hook() {
|
||||
zoxide add
|
||||
zoxide add "$(pwd -L)"
|
||||
}
|
||||
|
||||
[[ -n "${precmd_functions[(r)_zoxide_hook]}" ]] || {
|
||||
@ -26,7 +26,7 @@ _zoxide_hook() {
|
||||
const fn hook_pwd() -> Result<Cow<'static, str>> {
|
||||
const HOOK_PWD: &str = r#"
|
||||
_zoxide_hook() {
|
||||
zoxide add
|
||||
zoxide add "$(pwd -L)"
|
||||
}
|
||||
|
||||
chpwd_functions=(${chpwd_functions[@]} "_zoxide_hook")
|
||||
|
@ -25,7 +25,7 @@ fn remove(path: &str) -> Result<()> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let path = util::canonicalize(&path)?;
|
||||
let path = util::resolve_path(&path)?;
|
||||
let path = util::path_to_str(&path)?;
|
||||
|
||||
if let Some(idx) = db.dirs.iter().position(|dir| dir.path == path) {
|
||||
|
135
src/util.rs
135
src/util.rs
@ -1,9 +1,9 @@
|
||||
use crate::config;
|
||||
use crate::db::{Db, Epoch};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use std::env;
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
use std::time::SystemTime;
|
||||
|
||||
pub fn get_db() -> Result<Db> {
|
||||
@ -25,6 +25,135 @@ pub fn canonicalize<P: AsRef<Path>>(path: &P) -> Result<PathBuf> {
|
||||
dunce::canonicalize(path).with_context(|| format!("could not resolve path: {}", path.display()))
|
||||
}
|
||||
|
||||
/// Resolves the absolute version of a path.
|
||||
///
|
||||
/// If path is already absolute, the path is still processed to be cleaned, as it can contained ".." or "." (or other)
|
||||
/// character.
|
||||
/// If path is relative, use the current directory to build the absolute path.
|
||||
#[cfg(any(unix, windows))]
|
||||
pub fn resolve_path<P: AsRef<Path>>(path: P) -> Result<PathBuf> {
|
||||
let path = path.as_ref();
|
||||
let base_path;
|
||||
|
||||
let mut components = path.components().peekable();
|
||||
let mut stack = Vec::new();
|
||||
|
||||
// initialize root
|
||||
if cfg!(unix) {
|
||||
match components.peek() {
|
||||
Some(Component::RootDir) => {
|
||||
let root = components.next().unwrap();
|
||||
stack.push(root);
|
||||
}
|
||||
_ => {
|
||||
base_path = get_current_dir()?;
|
||||
stack.extend(base_path.components());
|
||||
}
|
||||
}
|
||||
} else if cfg!(windows) {
|
||||
use std::path::Prefix;
|
||||
|
||||
fn get_drive_letter<P: AsRef<Path>>(path: P) -> Option<u8> {
|
||||
let path = path.as_ref();
|
||||
let mut components = path.components();
|
||||
|
||||
match components.next() {
|
||||
Some(Component::Prefix(prefix)) => match prefix.kind() {
|
||||
Prefix::Disk(drive_letter) | Prefix::VerbatimDisk(drive_letter) => {
|
||||
Some(drive_letter)
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_drive_path(drive_letter: u8) -> PathBuf {
|
||||
format!(r"{}:\", drive_letter as char).into()
|
||||
}
|
||||
|
||||
fn get_drive_relative(drive_letter: u8) -> Result<PathBuf> {
|
||||
let path = get_current_dir()?;
|
||||
if Some(drive_letter) == get_drive_letter(&path) {
|
||||
return Ok(path);
|
||||
}
|
||||
|
||||
if let Some(path) = env::var_os(format!("={}:", drive_letter as char)) {
|
||||
return Ok(path.into());
|
||||
}
|
||||
|
||||
let path = get_drive_path(drive_letter);
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
match components.peek() {
|
||||
Some(Component::Prefix(prefix)) => match prefix.kind() {
|
||||
Prefix::Disk(drive_letter) => {
|
||||
let disk = components.next().unwrap();
|
||||
match components.peek() {
|
||||
Some(Component::RootDir) => {
|
||||
let root = components.next().unwrap();
|
||||
stack.push(disk);
|
||||
stack.push(root);
|
||||
}
|
||||
_ => {
|
||||
base_path = get_drive_relative(drive_letter)?;
|
||||
stack.extend(base_path.components());
|
||||
}
|
||||
}
|
||||
}
|
||||
Prefix::VerbatimDisk(drive_letter) => {
|
||||
components.next();
|
||||
if components.peek() == Some(&Component::RootDir) {
|
||||
components.next();
|
||||
}
|
||||
|
||||
base_path = get_drive_path(drive_letter);
|
||||
stack.extend(base_path.components());
|
||||
}
|
||||
_ => bail!("invalid path: {}", path.display()),
|
||||
},
|
||||
Some(Component::RootDir) => {
|
||||
components.next();
|
||||
|
||||
let current_dir = env::current_dir()?;
|
||||
let drive_letter = get_drive_letter(¤t_dir).with_context(|| {
|
||||
format!("could not get drive letter: {}", current_dir.display())
|
||||
})?;
|
||||
base_path = get_drive_path(drive_letter);
|
||||
stack.extend(base_path.components());
|
||||
}
|
||||
_ => {
|
||||
base_path = get_current_dir()?;
|
||||
stack.extend(base_path.components());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for component in components {
|
||||
match component {
|
||||
Component::Normal(_) => stack.push(component),
|
||||
Component::CurDir => (),
|
||||
Component::ParentDir => {
|
||||
if stack.last() != Some(&Component::RootDir) {
|
||||
stack.pop();
|
||||
}
|
||||
}
|
||||
Component::Prefix(_) | Component::RootDir => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
let result = stack.iter().collect::<PathBuf>();
|
||||
if !result.is_dir() {
|
||||
bail!("could not resolve path: {}", result.display());
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn get_current_dir() -> Result<PathBuf> {
|
||||
env::current_dir().context("could not get current path")
|
||||
}
|
||||
|
||||
pub fn path_to_str<P: AsRef<Path>>(path: &P) -> Result<&str> {
|
||||
let path = path.as_ref();
|
||||
path.to_str()
|
||||
|
Loading…
Reference in New Issue
Block a user