edkolev / promptline.vim

Generate a fast shell prompt with powerline symbols and airline colors
MIT License
623 stars 54 forks source link

cwd slice doesn't understand zsh static named directories #80

Open PrincessRTFM opened 4 years ago

PrincessRTFM commented 4 years ago

The cwd slice directly replaces $HOME with ~, which is nice but unfortunately ignores zsh static named directories (zsh manual, section 14.7 Filename Expansion, see subsection 2 Static named directories). Using print -P "%~" in zsh provides the cwd with such directories replaced, but this cannot simply be used in place of the existing line (local cwd="${PWD/#$HOME/$tilde}") because then any static named directory in the current directory path will be replaced with a single tilde, not including the static name:

It looks like the function was written with the assumption that if the current path starts with a ~ then it must indicate the user's home directory, which is reasonable given the way the current path is acquired. However, this means that one of the shell's built-in features (a default prompt will expand to use static directory names) is unavailable. Would it be possible to make the function intelligently determine if static named directories are in use from the shell-provided expanded prompt instead?

edkolev commented 4 years ago

Hi, thanks for the detailed description.

As a bash user, I'm not familiar with this "directory aliasing" feature. Do you have a working version for zsh? If yes, we can put the zsh-specific login behind an if like this one

PrincessRTFM commented 4 years ago

Unfortunately, I don't think I have the skill to put that together. I can spend a while testing things out and see what I can come up with though.

The quick rundown of zsh's static named directories feature is that you can define an alias using hash -d "alias_name=target_directory" much like the standard alias command. However, instead of making an actual alias, it extends the ~username-style directory expansion so that you can use ~your_alias_here at the start of an unquoted directory path, like home expansion, and it will expand to the targeted location. For instance, in my example above, I directory-aliased ~steam to my steam games installation directory, which allows me to cd ~steam instead of using the long path.

But as it happens, this doesn't actually need to be zsh-specific, except in how the current path is acquired. The print builtin for zsh might not be compatible with bash, dash, fish, etc, but once the path is retrieved, splitting it is shell-agnostic - just don't treat a leading character of ~ specially.

PrincessRTFM commented 4 years ago

Okay, I may have been wrong about not being able to make a working version for my request. I've narrowed things down a little, and I think the core fix is that $first_char shouldn't be... well, that. Instead of paying special attention to the first character like that, check the first path segment, before the very first / - either there's nothing there, so / is the first thing, or there's something there which suggests a directory alias. Instead of using $first_char, maybe use $leader or something, for clarity. I'm doing some scratchwork right now, and I think this looks promising. If it has to cut out (because it hit the $dir_limit) then there's no issue, it can still just set leader="$truncation" as now, but otherwise it would be printing the whole "leading text" which would be either / or (presumably) a tilde-prefixed directory alias. I'll tinker a little more and tell you what I can figure out.

PrincessRTFM commented 4 years ago

Hm. Another thing I notice, though I'm not sure how to solve, is that the truncation string is used even if the only piece being chopped off is a ~ or a /, which strikes me a little odd. Is that just me? I feel like ~/dir1/dir2 shouldn't be formatted into <truncation> | dir1 | dir2 in the prompt, since it's the same number of path segments either way.

PrincessRTFM commented 4 years ago

Okay, I think I fixed the original issue, and also found a workaround for the truncation thing. I'll leave it up to you whether you think the truncation thing is desired behaviour or not, but here's the output of my test showing the difference so you can get a better idea what I mean. If you want to do some more testing yourself, here's the code I used for my tests:

#!/bin/bash

# Your current method of getting the cwd
cwd="${PWD/#$HOME/"~"}"
formatted_cwd=""
part_count=0
dir_sep=" | " # Just for testing, use a pipe instead
leader="" # NOTE: this replaces first_char for clarity!
dir_limit=2 # I made this a number instead of a string, too
truncation="…"
# If you want to test a specific path, you can pass it to this testing script
[[ -n "$*" ]] && cwd="$*"

# Liberally applied debug statements
printf "Operating on '%s'\n\n" "$cwd"

# Grab the first character, same as the original did it
[[ -n ${ZSH_VERSION-} ]] && leader=$cwd[1,1] || leader=${cwd::1}
# If the first character is a tilde, grab the entire first segment - $cwd, with the LONGEST TRAILING string matching "/*" removed
[[ "$leader" == "~" ]] && leader="${cwd%%/*}"

# The original loop condition works fine
while [[ "$cwd" == */* && "$cwd" != "/" ]]; do
    # Gets the value of $cwd with the longest starting string matching "*/" removed
    part="${cwd##*/}"
    printf "Handling part %d: '%s'\n" $(($part_count + 1)) "$part"
    # Gets the value of $cwd with the shortest trailing string matching "/*" removed
    cwd="${cwd%/*}"
    printf "Remainder: '%s'\n" "$cwd"
    formatted_cwd="$dir_sep$part$formatted_cwd"
    printf "Currently handled: '%s'\n" "$formatted_cwd"
    part_count=$((part_count+1))
    printf "\n"
    [[ $part_count -eq $dir_limit ]] && leader="$truncation" && break
done
# If we're truncating, but the remaining path has ONLY ONE SEGMENT, just use the remaining path
[[ "$leader" == "$truncation" && "$cwd" != */* ]] && leader="$cwd"

printf "First character: '%s'\n" "$leader"
printf "Formatted CWD: '%s'\n" "$formatted_cwd"
printf "Remainder: '%s'\n" "$cwd"
printf "Final product: '%s%s'\n" "$leader" "$formatted_cwd"

As you can see, it was tested using plain bash, but running it through zsh ./prompts "~steam/Stardew Valley/Mods" produced the same output. I don't have any other shells, but given that I only used syntax that was already present in the original code, I would reasonably expect them to work too, unless the original code didn't work on them.

PrincessRTFM commented 4 years ago

I've manually edited my promptline exported file to set the __promptline_cwd function to the tweaked version, and using this code seems to work perfectly:

function __promptline_cwd {
  local dir_limit=2
  local truncation="…"
  local leader
  local part_count=0
  local formatted_cwd=""
  local dir_sep="  "
  local tilde="~"

  local cwd="${PWD/#$HOME/$tilde}"
  [[ -n ${ZSH_VERSION-} ]] && cwd=`print -P "%~"` # ADDITION: get zsh's cwd with its own expansion, to apply static named directory aliases

  # get first char of the path, i.e. tilde or slash
  [[ -n ${ZSH_VERSION-} ]] && leader=$cwd[1,1] || leader=${cwd::1}
  [[ "$leader" == "~" ]] && leader="${cwd%%/*}" # ADDITION: if the cwd starts with a tilde, grab the entire first segment in case of static named directory aliases

  # CHANGE: do not remove leading tilde
  #cwd="${cwd#\~}"

  while [[ "$cwd" == */* && "$cwd" != "/" ]]; do
    # pop off last part of cwd
    local part="${cwd##*/}"
    cwd="${cwd%/*}"

    formatted_cwd="$dir_sep$part$formatted_cwd"
    part_count=$((part_count+1))

    [[ $part_count -eq $dir_limit ]] && leader="$truncation" && break
  done
  [[ "$leader" == "$truncation" && "$cwd" != */* ]] && leader="$cwd" # ADDITION: if the truncated path is only one segment, don't truncate it

  printf "%s" "$leader$formatted_cwd"
}