nu_scripts/sourced/filesystem/cdpath.nu
2023-11-20 06:55:35 -06:00

170 lines
4.0 KiB
Plaintext

# You must set $env.CDPATH, try:
#
# $env.CDPATH = [
# ".",
# "~",
# "~/path/to/repositories",
# ]
#
# The above $env.CDPATH will complete:
# * Entries under the current directory ($env.PWD)
# * Entries in your home directory ($env.HOME)
# * Entries where you check out repositories
# * Children of those entries
#
# This CDPATH implementation also completes absolute paths to help you use `c`
# instead of `cd`
#
# Bugs:
# * `c` does not complete paths that start with "~". This should support both
# directories ("~/.config/nu"), users ("~notme"), and both combined
# ("~notme/.config/nu")
module cdpath {
# $env.CDPATH with unique, expanded, existing paths
def cdpath [] {
$env.CDPATH
| path expand
| uniq
| filter {|| $in | path exists }
}
# Children of a path
def children [path: string] {
ls -a $path
| where type == "dir"
| get name
}
# Completion for `c`
#
# `contains` is used instead of `starts-with` to behave similar to fuzzy
# completion behavior.
#
# During completion of a CDPATH entry the description contains the parent
# directory you will complete under. This allows you to tell which entry in
# your CDPATH your are completing to if you have the same directory under
# multiple entries.
def complete [context: string] {
let context_dir = $context | parse "c {context_dir}" | get context_dir | first
let path = $context_dir | path split
let no_trailing_slash = not ($context_dir | str ends-with "/")
# completion with no context
if ( $path | is-empty ) {
complete_from_cdpath
# Complete an entry in CDPATH
#
# This appends a / to allow continuation to the last step
} else if $no_trailing_slash and (1 == ( $path | length )) {
let first = $path | first
complete_from_cdpath
| filter {|| $in.value | str contains $first }
| upsert value {|| $"($in.value)/" }
# Complete a child of a CDPATH entry
} else {
let prefix = if 1 == ($path | length) {
$path | first
} else {
$path | first (($path | length) - 1) | path join
}
let last = if 1 == ($path | length) {
""
} else {
$path | last
}
let chosen_path = if ( $path | first) == "/" {
if $no_trailing_slash {
$prefix
} else {
$context_dir
}
} else {
cdpath
| each {||
$in | path join $prefix
}
| filter {||
$in | path exists
}
| first
}
children $chosen_path
| filter {||
$in | str contains $last
}
| each {|child|
$"($chosen_path | path join $child)/"
}
}
}
def complete_from_cdpath [] {
cdpath
| each { |path|
children $path
| path basename
| sort
| each { |child| { value: $child, description: $path } }
}
| flatten
| uniq-by value
}
# Change directory with $env.CDPATH
export def --env c [dir = "": string@complete] {
let span = (metadata $dir).span
let default = if $nu.os-info.name == "windows" {
$env.USERPROFILE
} else {
$env.HOME
}
let target_dir = if $dir == "" {
$default
} else if $dir == "-" {
if "OLDPWD" in $env {
$env.OLDPWD
} else {
$default
}
} else {
cdpath
| reduce -f "" { |$it, $acc|
if $acc == "" {
let new_path = ([$it $dir] | path join)
if ($new_path | path exists) {
$new_path
} else {
""
}
} else {
$acc
}
}
}
let target_dir = if $target_dir == "" {
let cdpath = $env.CDPATH | str join ", "
error make {
msg: $"No such child under: ($cdpath)",
label: {
text: "Child directory",
start: $span.start,
end: $span.end,
}
}
} else {
$target_dir
}
cd $target_dir
}
}
use cdpath c