Clearing line in vi mode deletes the line above - zsh

I run zsh in VI mode with a two-line prompt. Whenever I clear the line using 'cc' a line above my prompt is deleted. E.g. if I start with
magnus#tatooine 2502 ~
[I] %
magnus#tatooine 2502 ~
[I] %
Then I press escape and 'cc' I get
magnus#tatooine 2502 ~
magnus#tatooine 2502 ~
[I] %
It's very repeatable; I can clear the terminal by repeating the sequence multiple times. What do I need to do to get ZSH to clear the line without deleting the line above it?
My prompt is created using the following
zstyle ':vcs_info:*' enable git hg svn
zstyle ':vcs_info:(git*|hg*)' get-revision true
zstyle ':vcs_info:(git*|hg*)' check-for-changes true
zstyle ':vcs_info:git*' formats "(%s: %12.12i %c%u %b)" # hash changes branch
zstyle ':vcs_info:git*' actionformats "(%s(%F{yellow}%a%f): %12.12i %c%u %b)"
zstyle ':vcs_info:hg*:*' branchformat "%b" # only show branch
zstyle ':vcs_info:(hg*|git*):*' stagedstr "%F{green}S%f"
zstyle ':vcs_info:(hg*|git*):*' unstagedstr "%F{red}U%f"
zstyle ':vcs_info:git*+set-message:*' hooks git-st git-stash git-untracked
# improve the ZSH prompt in vi mode
function zle-line-init zle-keymap-select {
case ${KEYMAP} in
(main) prompt_vi_mode="%F{yellow}[I]%f"
;;
(vicmd) prompt_vi_mode="%F{red}[N]%f"
;;
esac
prompt_line1="%F{green}%n#%m%f %F{red}%h%f ${vcs_info_msg_0_} %F{blue}%~%f"
prompt_line2="${prompt_vi_mode} %B%#%b "
PS1="${prompt_line1}${prompt_newline}${prompt_line2}"
zle reset-prompt
}
zle -N zle-line-init
zle -N zle-keymap-select

Related

Collapse directories in zsh prompt in a unique way

Is there a way to collapse the current working directory in the zsh prompt in a unique way, so that I could copy and paste it to another terminal, hit TAB and get the original path?
Let's say we have following directories:
/adam/devl
/alice/devl
/alice/docs
/bob/docs
If the prompt is programmed to show the first characters, and I'm in /b/d, then it is unique. On the other hand, /a/d is not unique, so I would need /ad/d, /al/de and /al/do. And even /ali/… as soon as the user alex appears.
Is it possible to hack this directly in zsh or do I need to write a script, that finds the shortest unique beginning of each parent directory?
Thank you for your ideas!
I'm not aware that zsh has a built-in function of this sort, but it should be pretty easy to script without resorting to a single subshell or slow pipe:
#!/bin/zsh
paths=(${(s:/:)PWD})
cur_path='/'
cur_short_path='/'
for directory in ${paths[#]}
do
cur_dir=''
for (( i=0; i<${#directory}; i++ )); do
cur_dir+="${directory:$i:1}"
matching=("$cur_path"/"$cur_dir"*/)
if [[ ${#matching[#]} -eq 1 ]]; then
break
fi
done
cur_short_path+="$cur_dir/"
cur_path+="$directory/"
done
printf %q "${cur_short_path: : -1}"
echo
This script will output the shortest path needed for auto-completion to work.
You can throw it in your .zshrc as a function and then run it from any directory.
function spwd {
paths=(${(s:/:)PWD})
cur_path='/'
cur_short_path='/'
for directory in ${paths[#]}
do
cur_dir=''
for (( i=0; i<${#directory}; i++ )); do
cur_dir+="${directory:$i:1}"
matching=("$cur_path"/"$cur_dir"*/)
if [[ ${#matching[#]} -eq 1 ]]; then
break
fi
done
cur_short_path+="$cur_dir/"
cur_path+="$directory/"
done
printf %q "${cur_short_path: : -1}"
echo
}
Here's a video of it in action:
https://asciinema.org/a/0TyL8foqvQ8ec5ZHS3c1mn5LH
Or if you prefer, some sample output:
~/t $ ls
adam alice bob getshortcwd.zsh
~/t $ ls adam
devl
~/t $ ls alice
devl docs
~/t $ spwd
/h/v/t
~/t $ cd adam/devl
~/t/adam/devl $ spwd
/h/v/t/ad/d
~/t/adam/devl $ cd ../../alice/devl
~/t/alice/devl $ spwd
/h/v/t/al/de
~/t/alice/devl $ cd ../docs
~/t/alice/docs $ spwd
/h/v/t/al/do
~/t/alice/docs $ `spwd` [TAB]
~/t/alice/docs $ /h/v/t/al/do [TAB]
~/t/alice/docs $ /home/vsimonian/t/alice/docs
Yes it is possible to collapse the directories to a first-unique-letter path and have the Z Shell expand that path upon pressing [Tab]. I simply used compinstall (zsh utility script installed with Zsh) to generate the following code. The important part to take note of for expanding path elements is on the sixth zstyle command, near the end, where the bracket of characters to separate completion points with includes the /, which is, of course, the directory separator. With this, the unique paths you suggested will fully fill out with just one [Tab] press as if the * were at the end of each path-name unique letters.
# The following lines were added by compinstall
zstyle ':completion:*' add-space true
zstyle ':completion:*' completer _list _expand _complete _ignored _match _correct _approximate _prefix
zstyle ':completion:*' completions 1
zstyle ':completion:*' list-colors ${(s.:.)LS_COLORS}
zstyle ':completion:*' matcher-list 'm:{[:lower:]}={[:upper:]} r:|[._-]=* r:|=*' 'm:{[:lower:]}={[:upper:]} m:{[:lower:][:upper:]}={[:upper:][:lower:]} r:|[._-]=* r:|=*' 'r:|[._-/]=* r:|=*' 'l:|=* r:|=*'
zstyle ':completion:*' match-original both
zstyle :compinstall filename '/home/micah/.zsh/.zshrc'
autoload -Uz compinit
compinit
# End of lines added by compinstall
As for creating the unique path in the first place and inserting it into the prompt, it is possible with a zsh script or function, and therefore should be possible for the completer or line-editor also, but just sticking to the prompt, you would add a function to the $precmd_functions array that modifies or adds to the PS1 variable. This special array is a list of function names that are run right before each prompt.
function precmd_unique_pwd {
local pwd_string="$(upwd)"
PS1="%B%n#%m $pwd_string => %b"
}
precmd_functions+=( precmd_unique_pwd )
For getting of the current PWD in shortened form, I think this function is clear and easy to follow, though not necessarily optimized for low resource usage.
#!/bin/zsh
function upwd {
emulate -LR zsh -o nullglob
local dir Path
local -a newpwd tmp stack
local -i length=1 Flag=0
newpwd=( ${(s./.)PWD} )
foreach dir ( $newpwd )
(( length=0, Flag=0 ))
repeat $#dir
do
(( length += 1 ))
tmp=( ${(j.*/.)~stack}/$dir[1,$length]*(/) )
if
(( $#tmp == 1 ))
then
Path=$Path/$dir[1,$length]
stack+=( /$dir )
(( Flag=1 ))
break
fi
done
if
(( Flag ))
then
continue
else
Path=$Path/$dir
fi
end
print -- $Path
}
upwd
Notice that it finds unique paths with directory names because of the Zsh feature (/) at the end of the globbing. On the last directory (current) this means you may match something else if there is a file with the same name plus an extension.

How to determine if I'm in a hg or git directory via `:vcs_info:`?

:vcs_info: seems to know if you're in a git or hg directory if you enable it:
zstyle ':vcs_info:*' enable git hg
i.e., you can modify your prompt in this way.
How can I tap into that data so that I can write an if condition? e.g.
if [[ $some_magic_zstyle_vcs_info_variable ]]; then
echo "I'm in a git dir!"
fi
mafredi offered a better solution:
zstyle ':vcs_info:*' enable git hg
zstyle ':vcs_info:*' max-exports 3
zstyle ':vcs_info:(git|hg):*' formats ' %b' 'x%R' '%s'
zstyle ':vcs_info:(git|hg):*' actionformats ' %b|%a' 'x%R' '%s'
...
if [[ "$vcs_info_msg_2_" == "git" ]]; then
# git
fi
if [[ "$vcs_info_msg_2_" == "hg" ]]; then
# hg
fi
i.e., the %s will put the VCS name into $vcs_info_msg_2_.

widgets for case manipulation: `gU` in normal and `U` in visual mode

I reckon there's already a widget for the g~ action in zle. So g~iw will invert the case of a word.
I read the zshzle manual and did not find a widget that would give me the behaviour of gU (capitalize action) in vim.
for example: for the word "path_variable", with the cursor on the v, gUiW would change the world to "PATH_VARIABLE", and so on and so forth.
the widget capitalize-word does not seem to be the answer. I've tested it.
I also found that the key U in visual mode does not capitalize the visually selected text/region. I did not find a widget in the manual that would give me the desired behaviour either.
Is this a matter of writing a custom widget, or would one have to submit a patch upstream with c code changes? How can I bind gU in normal and U in visual mode to achieve the desired behaviour in zle vi-mode?
ZSH 5.3 will have pre built widgets for that. But if you can't wait, here it is:
# credits go to Oliver Kiddle <opk#zsh.org>,
# who personally shared these upper/lower widgets.
# I just corrected a small bug.
vi-lowercase() {
local save_cut="$CUTBUFFER"
local save_cur="$CURSOR"
zle .vi-change || return
zle .vi-cmd-mode
CUTBUFFER="${CUTBUFFER:l}"
if [[ $CURSOR = '0' ]]; then
zle .vi-put-before -n 1
else
zle .vi-put-after -n 1
fi
CUTBUFFER="$save_cut"
CURSOR="$save_cur"
}
vi-uppercase() {
local save_cut="$CUTBUFFER"
local save_cur="$CURSOR"
zle .vi-change || return
zle .vi-cmd-mode
CUTBUFFER="${CUTBUFFER:u}"
if [[ $CURSOR = '0' ]]; then
zle .vi-put-before -n 1
else
zle .vi-put-after -n 1
fi
CUTBUFFER="$save_cut"
CURSOR="$save_cur"
}
# can safely disable this after commit zsh commit #a73ae70 (zsh-5.2-301- ga73ae70)
zle -N vi-lowercase
zle -N vi-uppercase
bindkey -a 'gU' vi-uppercase
bindkey -a 'gu' vi-lowercase
bindkey -M visual 'u' vi-lowercase
bindkey -M visual 'U' vi-uppercase

Why doesn't incremental history search work in zsh?

I'm trying to get rid of Oh-my-zsh and currently my biggest challenge is getting incremental history search working. I've acked and grepped my way through the code and there's no clues in site. I get this error :
No such widget 'histrory-beginning-search-backward'
EDIT:
I've tried this with the absolute least possible code, it still doesn't work. here's the reduced test case:
# key bindings
bindkey -v
function zle-line-init zle-keymap-select {
RPS1="${${KEYMAP/vicmd/NORMAL}/(main|viins)/INSERT}"
RPS2=$RPS1
zle reset-prompt
}
zle -N zle-line-init
zle -N zle-keymap-select
bindkey -M vicmd '^R' histrory-beginning-search-backward
bindkey -M viins '^R' histrory-beginning-search-backward
Here's my config, taken from the zsh config wizzard, my current zshrc and oh-my-zsh:
autoload zkbd
# The following lines were added by compinstall
zstyle ':completion:*' completer _expand _complete _ignored _correct _approximate
zstyle ':completion:*' matcher-list 'm:{a-zA-Z}={A-Za-z}' 'r:|[._-]=* r:|=*' 'l:|=* r:|=*'zstyle ':completion:*' verbose true
zstyle :compinstall filename '/home/jonathan/.zshrc'
zstyle ':completion:*' list-colors ''
zstyle ':completion::complete:*' use-cache 1
zstyle ':completion::complete:*' cache-path $HOME/.cache/zsh
#load colorstuff
autoload -Uz compinit
autoload -U colors && colors
compinit
# End of lines added by compinstall
# Lines configured by zsh-newuser-install
HISTFILE=~/.histfile
HISTSIZE=1000
SAVEHIST=1000
setopt extendedglob
unsetopt beep nomatch notify
# End of lines configured by zsh-newuser-install
setopt completealiases
# Ignore boring stuff
zstyle ':completion:*:*:*:users' ignored-patterns \
adm amanda apache at avahi avahi-autoipd beaglidx bin cacti canna \
clamav daemon dbus distcache dnsmasq dovecot fax ftp games gdm \
gkrellmd gopher hacluster haldaemon halt hsqldb ident junkbust kdm \
ldap lp mail mailman mailnull man messagebus mldonkey mysql nagios \
named netdump news nfsnobody nobody nscd ntp nut nx obsrun openvpn \
operator pcap polkitd postfix postgres privoxy pulse pvm quagga radvd \
rpc rpcuser rpm rtkit scard shutdown squid sshd statd svn sync tftp \
usbmux uucp vcsa wwwrun xfs '_*'
zstyle ':completion:*:functions' ignored-patterns '_*'
# Completion dots
expand-or-complete-with-dots() {
echo -n "\e[31m......\e[0m"
zle expand-or-complete
zle redisplay
}
zle -N expand-or-complete-with-dots
bindkey "^I" expand-or-complete-with-dots
# Prompt stuff *truncated*
# key bindings
bindkey -v
function zle-line-init zle-keymap-select {
RPS1="${${KEYMAP/vicmd/NORMAL}/(main|viins)/INSERT}"
RPS2=$RPS1
zle reset-prompt
}
zle -N zle-line-init
zle -N zle-keymap-select
bindkey -M vicmd '^R' histrory-beginning-search-backward
bindkey -M viins '^R' histrory-beginning-search-backward
function run-again {
zle up-history
zle accept-line
}
zle -N run-again
bindkey -M viins '^W' run-again
bindkey -M vicmd '^W' run-again
bindkey -M viins ' ' magic-space # [Space] - do history expansion
bindkey '^[[1;5C' forward-word # [Ctrl-RightArrow] - move forward one word
bindkey '^[[1;5D' backward-word # [Ctrl-LeftArrow] - move backward one word
if [[ "${terminfo[kcbt]}" != "" ]]; then
bindkey -M viins "${terminfo[kcbt]}" reverse-menu-complete # [Shift-Tab] - move through the completion menu backwards
fi
dirstackfile="$HOME/.cache/zsh/dirs"
if [[ -f dirstackfile ]] && [[ $#dirstack -eq 0 ]]; then
dirstack=( ${(f)"$(< $dirstackfile)"} )
[[ -d $dirstack[1] ]] && builtin cd $dirstack[1]
fi
chpwd() {
print -l $PWD ${(u)dirstack} > $dirstackfile
}
dirstacksize=20
setopt autopushd pushdsilent pushdtohome
# remove duplicate entries
setopt pushdignoredups
#revert plus and minus operators.
setopt pushdminus
#PATH info truncated
What am I doing wrong?
Whoops... I had a typo, now I feel dumb. I had it as histrory, when it should be history..

zsh alias expansion

Is it possible to configure zsh to expand global aliases during tab completion? For example, I have the common aliases:
alias -g '...'='../..'
alias -g '....'='../../..'
but when I type, for example, cd .../some<tab> it won't expand to cd .../something or cd ../../something. Consequently, I frequently won't use these handy aliases because they are incompatible with tab completion.
I'm a user of Mikael Magnusson's rationalise-dot. From my zshrc:
# This was written entirely by Mikael Magnusson (Mikachu)
# Basically type '...' to get '../..' with successive .'s adding /..
function rationalise-dot {
local MATCH # keep the regex match from leaking to the environment
if [[ $LBUFFER =~ '(^|/| | |'$'\n''|\||;|&)\.\.$' ]]; then
LBUFFER+=/
zle self-insert
zle self-insert
else
zle self-insert
fi
}
zle -N rationalise-dot
bindkey . rationalise-dot
# without this, typing a . aborts incremental history search
bindkey -M isearch . self-insert
Try looking up zsh abbreviations. They allow you to enter an "abbreviation" which automatically gets replaced with its full form when you hit a magic key such as space. So you can create one which changes ...<SPACE> to ../...
For example, this is what you need in your profile:
typeset -A abbrevs
abbrevs=(
"..." "../.."
"...." "../../.."
)
#create aliases for the abbrevs too
for abbr in ${(k)abbrevs}; do
alias -g $abbr="${abbrevs[$abbr]}"
done
my-expand-abbrev() {
local MATCH
LBUFFER=${LBUFFER%%(#m)[_a-zA-Z0-9]#}
LBUFFER+=${abbrevs[$MATCH]:-$MATCH}
zle self-insert
}
bindkey " " my-expand-abbrev

Resources