|
|
# |
|
|
# Sets key bindings. |
|
|
# |
|
|
# Authors: |
|
|
# Sorin Ionescu <sorin.ionescu@gmail.com> |
|
|
# |
|
|
|
|
|
# Return if requirements are not found. |
|
|
if [[ "$TERM" == 'dumb' ]]; then |
|
|
return 1 |
|
|
fi |
|
|
|
|
|
# |
|
|
# Options |
|
|
# |
|
|
|
|
|
setopt BEEP # Beep on error in line editor. |
|
|
|
|
|
# |
|
|
# Variables |
|
|
# |
|
|
|
|
|
# Treat these characters as part of a word. |
|
|
WORDCHARS='*?_-.[]~&;!#$%^(){}<>' |
|
|
|
|
|
# Use human-friendly identifiers. |
|
|
zmodload zsh/terminfo |
|
|
typeset -gA key_info |
|
|
key_info=( |
|
|
'Control' '\C-' |
|
|
'ControlLeft' '\e[1;5D \e[5D \e\e[D \eOd' |
|
|
'ControlRight' '\e[1;5C \e[5C \e\e[C \eOc' |
|
|
'ControlPageUp' '\e[5;5~' |
|
|
'ControlPageDown' '\e[6;5~' |
|
|
'Escape' '\e' |
|
|
'Meta' '\M-' |
|
|
'Backspace' "^?" |
|
|
'Delete' "^[[3~" |
|
|
'F1' "$terminfo[kf1]" |
|
|
'F2' "$terminfo[kf2]" |
|
|
'F3' "$terminfo[kf3]" |
|
|
'F4' "$terminfo[kf4]" |
|
|
'F5' "$terminfo[kf5]" |
|
|
'F6' "$terminfo[kf6]" |
|
|
'F7' "$terminfo[kf7]" |
|
|
'F8' "$terminfo[kf8]" |
|
|
'F9' "$terminfo[kf9]" |
|
|
'F10' "$terminfo[kf10]" |
|
|
'F11' "$terminfo[kf11]" |
|
|
'F12' "$terminfo[kf12]" |
|
|
'Insert' "$terminfo[kich1]" |
|
|
'Home' "$terminfo[khome]" |
|
|
'PageUp' "$terminfo[kpp]" |
|
|
'End' "$terminfo[kend]" |
|
|
'PageDown' "$terminfo[knp]" |
|
|
'Up' "$terminfo[kcuu1]" |
|
|
'Left' "$terminfo[kcub1]" |
|
|
'Down' "$terminfo[kcud1]" |
|
|
'Right' "$terminfo[kcuf1]" |
|
|
'BackTab' "$terminfo[kcbt]" |
|
|
) |
|
|
|
|
|
# Set empty $key_info values to an invalid UTF-8 sequence to induce silent |
|
|
# bindkey failure. |
|
|
for key in "${(k)key_info[@]}"; do |
|
|
if [[ -z "$key_info[$key]" ]]; then |
|
|
key_info[$key]='<EFBFBD>' |
|
|
fi |
|
|
done |
|
|
|
|
|
# |
|
|
# External Editor |
|
|
# |
|
|
|
|
|
# Allow command line editing in an external editor. |
|
|
autoload -Uz edit-command-line |
|
|
zle -N edit-command-line |
|
|
|
|
|
# |
|
|
# Functions |
|
|
# |
|
|
# Runs bindkey but for all of the keymaps. Running it with no arguments will |
|
|
# print out the mappings for all of the keymaps. |
|
|
function bindkey-all { |
|
|
local keymap='' |
|
|
for keymap in $(bindkey -l); do |
|
|
[[ "$#" -eq 0 ]] && printf "#### %s\n" "${keymap}" 1>&2 |
|
|
bindkey -M "${keymap}" "$@" |
|
|
done |
|
|
} |
|
|
# Exposes information about the Zsh Line Editor via the $editor_info associative |
|
|
# array. |
|
|
function editor-info { |
|
|
# Clean up previous $editor_info. |
|
|
unset editor_info |
|
|
typeset -gA editor_info |
|
|
|
|
|
if [[ "$KEYMAP" == 'vicmd' ]]; then |
|
|
zstyle -s ':prezto:module:editor:info:keymap:alternate' format 'REPLY' |
|
|
editor_info[keymap]="$REPLY" |
|
|
else |
|
|
zstyle -s ':prezto:module:editor:info:keymap:primary' format 'REPLY' |
|
|
editor_info[keymap]="$REPLY" |
|
|
|
|
|
if [[ "$ZLE_STATE" == *overwrite* ]]; then |
|
|
zstyle -s ':prezto:module:editor:info:keymap:primary:overwrite' format 'REPLY' |
|
|
editor_info[overwrite]="$REPLY" |
|
|
else |
|
|
zstyle -s ':prezto:module:editor:info:keymap:primary:insert' format 'REPLY' |
|
|
editor_info[overwrite]="$REPLY" |
|
|
fi |
|
|
fi |
|
|
|
|
|
unset REPLY |
|
|
zle zle-reset-prompt |
|
|
} |
|
|
zle -N editor-info |
|
|
|
|
|
# Reset the prompt based on the current context and |
|
|
# the ps-context option. |
|
|
function zle-reset-prompt { |
|
|
if zstyle -t ':prezto:module:editor' ps-context; then |
|
|
# If we aren't within one of the specified contexts, then we want to reset |
|
|
# the prompt with the appropriate editor_info[keymap] if there is one. |
|
|
if [[ $CONTEXT != (select|cont) ]]; then |
|
|
zle reset-prompt |
|
|
zle -R |
|
|
fi |
|
|
else |
|
|
zle reset-prompt |
|
|
zle -R |
|
|
fi |
|
|
} |
|
|
zle -N zle-reset-prompt |
|
|
|
|
|
# Updates editor information when the keymap changes. |
|
|
function zle-keymap-select { |
|
|
zle editor-info |
|
|
} |
|
|
zle -N zle-keymap-select |
|
|
|
|
|
# Enables terminal application mode and updates editor information. |
|
|
function zle-line-init { |
|
|
# The terminal must be in application mode when ZLE is active for $terminfo |
|
|
# values to be valid. |
|
|
if (( $+terminfo[smkx] )); then |
|
|
# Enable terminal application mode. |
|
|
echoti smkx |
|
|
fi |
|
|
|
|
|
# Update editor information. |
|
|
zle editor-info |
|
|
} |
|
|
zle -N zle-line-init |
|
|
|
|
|
# Disables terminal application mode and updates editor information. |
|
|
function zle-line-finish { |
|
|
# The terminal must be in application mode when ZLE is active for $terminfo |
|
|
# values to be valid. |
|
|
if (( $+terminfo[rmkx] )); then |
|
|
# Disable terminal application mode. |
|
|
echoti rmkx |
|
|
fi |
|
|
|
|
|
# Update editor information. |
|
|
zle editor-info |
|
|
} |
|
|
zle -N zle-line-finish |
|
|
|
|
|
# Toggles emacs overwrite mode and updates editor information. |
|
|
function overwrite-mode { |
|
|
zle .overwrite-mode |
|
|
zle editor-info |
|
|
} |
|
|
zle -N overwrite-mode |
|
|
|
|
|
# Enters vi insert mode and updates editor information. |
|
|
function vi-insert { |
|
|
zle .vi-insert |
|
|
zle editor-info |
|
|
} |
|
|
zle -N vi-insert |
|
|
|
|
|
# Moves to the first non-blank character then enters vi insert mode and updates |
|
|
# editor information. |
|
|
function vi-insert-bol { |
|
|
zle .vi-insert-bol |
|
|
zle editor-info |
|
|
} |
|
|
zle -N vi-insert-bol |
|
|
|
|
|
# Enters vi replace mode and updates editor information. |
|
|
function vi-replace { |
|
|
zle .vi-replace |
|
|
zle editor-info |
|
|
} |
|
|
zle -N vi-replace |
|
|
|
|
|
# Expands .... to ../.. |
|
|
function expand-dot-to-parent-directory-path { |
|
|
if [[ $LBUFFER = *.. ]]; then |
|
|
LBUFFER+='/..' |
|
|
else |
|
|
LBUFFER+='.' |
|
|
fi |
|
|
} |
|
|
zle -N expand-dot-to-parent-directory-path |
|
|
|
|
|
# Displays an indicator when completing. |
|
|
function expand-or-complete-with-indicator { |
|
|
local indicator |
|
|
zstyle -s ':prezto:module:editor:info:completing' format 'indicator' |
|
|
|
|
|
# This is included to work around a bug in zsh which shows up when interacting |
|
|
# with multi-line prompts. |
|
|
if [[ -z "$indicator" ]]; then |
|
|
zle expand-or-complete |
|
|
return |
|
|
fi |
|
|
|
|
|
print -Pn "$indicator" |
|
|
zle expand-or-complete |
|
|
zle redisplay |
|
|
} |
|
|
zle -N expand-or-complete-with-indicator |
|
|
|
|
|
# Inserts 'sudo ' at the beginning of the line. |
|
|
function prepend-sudo { |
|
|
if [[ "$BUFFER" != su(do|)\ * ]]; then |
|
|
BUFFER="sudo $BUFFER" |
|
|
(( CURSOR += 5 )) |
|
|
fi |
|
|
} |
|
|
zle -N prepend-sudo |
|
|
|
|
|
# Expand aliases |
|
|
function glob-alias { |
|
|
zle _expand_alias |
|
|
zle expand-word |
|
|
zle magic-space |
|
|
} |
|
|
zle -N glob-alias |
|
|
|
|
|
# Toggle the comment character at the start of the line. This is meant to work |
|
|
# around a buggy implementation of pound-insert in zsh. |
|
|
# |
|
|
# This is currently only used for the emacs keys because vi-pound-insert has |
|
|
# been reported to work properly. |
|
|
function pound-toggle { |
|
|
if [[ "$BUFFER" = '#'* ]]; then |
|
|
# Because of an oddity in how zsh handles the cursor when the buffer size |
|
|
# changes, we need to make this check before we modify the buffer and let |
|
|
# zsh handle moving the cursor back if it's past the end of the line. |
|
|
if [[ $CURSOR != $#BUFFER ]]; then |
|
|
(( CURSOR -= 1 )) |
|
|
fi |
|
|
BUFFER="${BUFFER:1}" |
|
|
else |
|
|
BUFFER="#$BUFFER" |
|
|
(( CURSOR += 1 )) |
|
|
fi |
|
|
} |
|
|
zle -N pound-toggle |
|
|
|
|
|
# Reset to default key bindings. |
|
|
bindkey -d |
|
|
|
|
|
# |
|
|
# Emacs Key Bindings |
|
|
# |
|
|
|
|
|
for key in "$key_info[Escape]"{B,b} "${(s: :)key_info[ControlLeft]}" \ |
|
|
"${key_info[Escape]}${key_info[Left]}" |
|
|
bindkey -M emacs "$key" emacs-backward-word |
|
|
for key in "$key_info[Escape]"{F,f} "${(s: :)key_info[ControlRight]}" \ |
|
|
"${key_info[Escape]}${key_info[Right]}" |
|
|
bindkey -M emacs "$key" emacs-forward-word |
|
|
|
|
|
# Kill to the beginning of the line. |
|
|
for key in "$key_info[Escape]"{K,k} |
|
|
bindkey -M emacs "$key" backward-kill-line |
|
|
|
|
|
# Redo. |
|
|
bindkey -M emacs "$key_info[Escape]_" redo |
|
|
|
|
|
# Search previous character. |
|
|
bindkey -M emacs "$key_info[Control]X$key_info[Control]B" vi-find-prev-char |
|
|
|
|
|
# Match bracket. |
|
|
bindkey -M emacs "$key_info[Control]X$key_info[Control]]" vi-match-bracket |
|
|
|
|
|
# Edit command in an external editor. |
|
|
bindkey -M emacs "$key_info[Control]X$key_info[Control]E" edit-command-line |
|
|
|
|
|
if (( $+widgets[history-incremental-pattern-search-backward] )); then |
|
|
bindkey -M emacs "$key_info[Control]R" \ |
|
|
history-incremental-pattern-search-backward |
|
|
bindkey -M emacs "$key_info[Control]S" \ |
|
|
history-incremental-pattern-search-forward |
|
|
fi |
|
|
|
|
|
# Toggle comment at the start of the line. Note that we use pound-toggle which |
|
|
# is similar to pount insert, but meant to work around some issues that were |
|
|
# being seen in iTerm. |
|
|
bindkey -M emacs "$key_info[Escape];" pound-toggle |
|
|
|
|
|
|
|
|
# |
|
|
# Vi Key Bindings |
|
|
# |
|
|
|
|
|
# Edit command in an external editor emacs style (v is used for visual mode) |
|
|
bindkey -M vicmd "$key_info[Control]X$key_info[Control]E" edit-command-line |
|
|
|
|
|
# Undo/Redo |
|
|
bindkey -M vicmd "u" undo |
|
|
bindkey -M vicmd "$key_info[Control]R" redo |
|
|
|
|
|
if (( $+widgets[history-incremental-pattern-search-backward] )); then |
|
|
bindkey -M vicmd "?" history-incremental-pattern-search-backward |
|
|
bindkey -M vicmd "/" history-incremental-pattern-search-forward |
|
|
else |
|
|
bindkey -M vicmd "?" history-incremental-search-backward |
|
|
bindkey -M vicmd "/" history-incremental-search-forward |
|
|
fi |
|
|
|
|
|
# Toggle comment at the start of the line. |
|
|
bindkey -M vicmd "#" vi-pound-insert |
|
|
|
|
|
# |
|
|
# Emacs and Vi Key Bindings |
|
|
# |
|
|
|
|
|
# Unbound keys in vicmd and viins mode will cause really odd things to happen |
|
|
# such as the casing of all the characters you have typed changing or other |
|
|
# undefined things. In emacs mode they just insert a tilde, but bind these keys |
|
|
# in the main keymap to a noop op so if there is no keybind in the users mode |
|
|
# it will fall back and do nothing. |
|
|
function _prezto-zle-noop { ; } |
|
|
zle -N _prezto-zle-noop |
|
|
local -a unbound_keys |
|
|
unbound_keys=( |
|
|
"${key_info[F1]}" |
|
|
"${key_info[F2]}" |
|
|
"${key_info[F3]}" |
|
|
"${key_info[F4]}" |
|
|
"${key_info[F5]}" |
|
|
"${key_info[F6]}" |
|
|
"${key_info[F7]}" |
|
|
"${key_info[F8]}" |
|
|
"${key_info[F9]}" |
|
|
"${key_info[F10]}" |
|
|
"${key_info[F11]}" |
|
|
"${key_info[F12]}" |
|
|
"${key_info[PageUp]}" |
|
|
"${key_info[PageDown]}" |
|
|
"${key_info[ControlPageUp]}" |
|
|
"${key_info[ControlPageDown]}" |
|
|
) |
|
|
for keymap in $unbound_keys; do |
|
|
bindkey -M viins "${keymap}" _prezto-zle-noop |
|
|
bindkey -M vicmd "${keymap}" _prezto-zle-noop |
|
|
done |
|
|
|
|
|
# Keybinds for all keymaps |
|
|
for keymap in 'emacs' 'viins' 'vicmd'; do |
|
|
bindkey -M "$keymap" "$key_info[Home]" beginning-of-line |
|
|
bindkey -M "$keymap" "$key_info[End]" end-of-line |
|
|
done |
|
|
|
|
|
# Keybinds for all vi keymaps |
|
|
for keymap in viins vicmd; do |
|
|
# Ctrl + Left and Ctrl + Right bindings to forward/backward word |
|
|
for key in "${(s: :)key_info[ControlLeft]}" |
|
|
bindkey -M "$keymap" "$key" vi-backward-word |
|
|
for key in "${(s: :)key_info[ControlRight]}" |
|
|
bindkey -M "$keymap" "$key" vi-forward-word |
|
|
done |
|
|
|
|
|
# Keybinds for emacs and vi insert mode |
|
|
for keymap in 'emacs' 'viins'; do |
|
|
bindkey -M "$keymap" "$key_info[Insert]" overwrite-mode |
|
|
bindkey -M "$keymap" "$key_info[Delete]" delete-char |
|
|
bindkey -M "$keymap" "$key_info[Backspace]" backward-delete-char |
|
|
|
|
|
bindkey -M "$keymap" "$key_info[Left]" backward-char |
|
|
bindkey -M "$keymap" "$key_info[Right]" forward-char |
|
|
|
|
|
# Expand history on space. |
|
|
bindkey -M "$keymap" ' ' magic-space |
|
|
|
|
|
# Clear screen. |
|
|
bindkey -M "$keymap" "$key_info[Control]L" clear-screen |
|
|
|
|
|
# Expand command name to full path. |
|
|
for key in "$key_info[Escape]"{E,e} |
|
|
bindkey -M "$keymap" "$key" expand-cmd-path |
|
|
|
|
|
# Duplicate the previous word. |
|
|
for key in "$key_info[Escape]"{M,m} |
|
|
bindkey -M "$keymap" "$key" copy-prev-shell-word |
|
|
|
|
|
# Use a more flexible push-line. |
|
|
for key in "$key_info[Control]Q" "$key_info[Escape]"{q,Q} |
|
|
bindkey -M "$keymap" "$key" push-line-or-edit |
|
|
|
|
|
# Bind Shift + Tab to go to the previous menu item. |
|
|
bindkey -M "$keymap" "$key_info[BackTab]" reverse-menu-complete |
|
|
|
|
|
# Complete in the middle of word. |
|
|
bindkey -M "$keymap" "$key_info[Control]I" expand-or-complete |
|
|
|
|
|
# Expand .... to ../.. |
|
|
if zstyle -t ':prezto:module:editor' dot-expansion; then |
|
|
bindkey -M "$keymap" "." expand-dot-to-parent-directory-path |
|
|
fi |
|
|
|
|
|
# Display an indicator when completing. |
|
|
bindkey -M "$keymap" "$key_info[Control]I" \ |
|
|
expand-or-complete-with-indicator |
|
|
|
|
|
# Insert 'sudo ' at the beginning of the line. |
|
|
bindkey -M "$keymap" "$key_info[Control]X$key_info[Control]S" prepend-sudo |
|
|
|
|
|
# control-space expands all aliases, including global |
|
|
bindkey -M "$keymap" "$key_info[Control] " glob-alias |
|
|
done |
|
|
|
|
|
# Delete key deletes character in vimcmd cmd mode instead of weird default functionality |
|
|
bindkey -M vicmd "$key_info[Delete]" delete-char |
|
|
|
|
|
# Do not expand .... to ../.. during incremental search. |
|
|
if zstyle -t ':prezto:module:editor' dot-expansion; then |
|
|
bindkey -M isearch . self-insert 2> /dev/null |
|
|
fi |
|
|
|
|
|
# |
|
|
# Layout |
|
|
# |
|
|
|
|
|
# Set the key layout. |
|
|
zstyle -s ':prezto:module:editor' key-bindings 'key_bindings' |
|
|
if [[ "$key_bindings" == (emacs|) ]]; then |
|
|
bindkey -e |
|
|
elif [[ "$key_bindings" == vi ]]; then |
|
|
bindkey -v |
|
|
else |
|
|
print "prezto: editor: invalid key bindings: $key_bindings" >&2 |
|
|
fi |
|
|
|
|
|
unset key{,map,_bindings}
|
|
|
|