Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved CDPATH #644

Merged
merged 1 commit into from
Oct 19, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 156 additions & 23 deletions sourced/filesystem/cdpath.nu
Original file line number Diff line number Diff line change
@@ -1,36 +1,169 @@
def-env c [dir = ""] {
let default = if $nu.os-info.name == "windows" {
$env.USERPROFILE
# 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 {
$env.HOME
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)/"
}
}
}

let complete_dir = if $dir == "" {
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 {
$env.CDPATH
| reduce -f "" { |$it, $acc| if $acc == "" {
let new_path = ([$it $dir] | path join)
cdpath
| reduce -f "" { |$it, $acc|
if $acc == "" {
let new_path = ([$it $dir] | path join)
if ($new_path | path exists) {
$new_path
$new_path
} else {
""
""
}
} else { $acc }}
} else {
$acc
}
}
}

let complete_dir = if $complete_dir == "" {
error make --unspanned {msg: "No such path"}
} else if (($complete_dir | path expand | path type) != "dir") {
error make --unspanned {msg: "Not a directory"}
} else {
($complete_dir | path expand)

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 $complete_dir
cd $target_dir
}
}

# You need to have $env.CDPATH variable declared, my suggestion from config.nu:
# $env.CDPATH = [".", $env.HOME, "/", ([$env.HOME, ".config"] | path join)]
# WINDOWS:
# $env.CDPATH = ["", $env.USERPROFILE, ([$env.USERPROFILE, "AppData\\Roaming\\"] | path join)]
use cdpath c