10 октября 2007

Vim like zsh keymapping


Данная статья посвящена исключительно настройке клавиатурных привязок в zsh в стиле vim, во избежание повторений общих советов в статье нет.

В одном из обзоров редактора vim я встретил фразу, смысл которой сводится к следующему - очень многое в UNIX основано на подобии, поэтому изучение данного редактора для относительно опытного пользователя не должно представлять трудности.
Многие разработчики отталкиваются от этого подобия и делают приложения чем-то похожими либо на vim, либо на emacs, либо на оба редактора. Именно так поступили создатели zsh, предоставив пользователю на выбор четыре схемы "привязок горячих клавиш" для редактора коандной строки - emacs, viins, vicmd и .safe (две схемы для vim для эмуляции разных его режимов).
Я люблю vim и постоянно использую его и только его, под какой бы операционной системой ни оказался, благо его порты есть под самую разнообразную экзотику. И в ходе очередного витка изысканий по настройке zsh было принято решение - для эффективности и унификации использовать схему привязок vim. Однако чувство незаконченности и неудобства не покидало меня. И выход был найден - создать свои схемы привязок на основе уже существующих, воспользовавшись поистине безграничными возможностями настройки zsh.
Итак, открываем ~/.zshrc.

1. Создадим две схемы на основе существующих (по одной на режим вставки и командный):

#vi insert mode like keybindings
#defining something more usable than the defaults vi bindings
#creating two keymaps
bindkey -N myviins viins
bindkey -N myvicmd vicmd

2. Исправим досадное недоразумение - zsh никак не отображает текущий режим - напишем свои переключения:

#defining widgets, to switch between them
function my_viins_to_vicmd(){print -n "\033]0;zsh\a";bindkey -A myvicmd main}
function my_vicmd_to_viinsi(){print -n "\033]0;zsh INSERT\a";bindkey -A myviins main}
function my_vicmd_to_viinsa(){print -n "\033]0;zsh INSERT\a";zle vi-forward-char;bindkey -A myviins main}
zle -N my_viins_to_vicmd
zle -N my_vicmd_to_viinsi
zle -N my_vicmd_to_viinsa
bindkey -M myviins '^[' my_viins_to_vicmd
bindkey -M myvicmd 'i' my_vicmd_to_viinsi
bindkey -M myvicmd 'a' my_vicmd_to_viinsa

Теперь в заголовке эмулятора терминала будет отображаться режим вставки.

3. Небольшая полезняшка - памятка с текущими привязками:

function list_mappings(){bindkey}; zle -N list_mappings
bindkey -M myvicmd ':map' list_mappings

4. сделаем свой режим вставки режимом по умолчанию:

#setting my vi-like insert mode by default
bindkey -A myviins main

5. Исправим непривычное поведение некоторых клавиш (адаптация общего совета Алексея Федорчука)

#making work some special keys
bindkey "\e[2~" yank
bindkey "\e[3~" delete-char
bindkey "\e[5~" up-line-or-history
bindkey "\e[6~" down-line-or-history
bindkey "\e[A" up-line-or-search ## up arrow for back-history-search
bindkey "\e[B" down-line-or-search ## down arrow for fwd-history-search

#making work Home and End keys in both modes
case $TERM in
linux)
bindkey -M myviins "\e[1~" beginning-of-line
bindkey -M myviins "\e[4~" end-of-line
bindkey -M myvicmd "^[[1~" beginning-of-line
bindkey -M myvicmd "^[[4~" end-of-line
;;
*xterm*|rxvt|(dt|k|E)term)
bindkey -M myviins "\e[H" beginning-of-line
bindkey -M myviins "\e[F" end-of-line
bindkey -M myvicmd "\e[H" beginning-of-line
bindkey -M myvicmd "\e[F" end-of-line
;;
esac

Ну и напоследок небольшой пример, показывающий, как писать виджеты ("задача виджета выполнять некое малое действие") в стиле команд vim и привязывать их к клавиатурным сочетаниям.
Иногда возникает потребность быстро заменить один из аргументов в вызове программы:

kill -HUP foobar
kill -9 foobar

это и будет делать мой виджет.
Сначала объявляем функцию, которая будет это делать:

#simple widget, wich deletes N-th parameter (word) in line
killparam()
{
zle beginning-of-line
zle vi-forward-word -n ${NUMERIC:-1}
zle delete-word -n 1
zle vi-delete-char -n 1
zle my_vicmd_to_viinsi
}

затем провозглашаем её виджетом:

zle -N killparam

и привязываем к горячей клавише:

bindkey -M myvicmd 'k' killparam

При написании виджета для простоты я использовал только уже готовые виджеты самой zsh, вызывая их с помощью команды zle. Обратите внимание на передачу параметра второму из них - если $NUMERIC не определён, будет передана еденица. Всем остальным виджетам предписано выполняться строго один раз, даже если $NUMERIC задан (-n 1).
Теперь, чтобы удалить -HUP в указанном примере достаточно в режиме редактирования набрать 1k или просто k ( если набрать 2k, то будет удалено второе слово и так далее).
Таким образом я избавился от неинформативности оболочки, странного поведения привычных вещей и научил её выполнять новые трюки.

2 комментария:

Unknown комментирует...

Спасибо, хорошая статья. Только вот отображение текущего режима у меня так и не запустилось (SuSE 10.0). И тестовый виджет не запускается. В чем может быть дело?

PhoeniX комментирует...

Наверное, пункт 2 не со всеми терминалами работает. У меня работает с konsole, xterm и screen.
Скорее всего, у вас просто не включены виджеты zle или настроены неправильно. Вот, например, что сказала мне оболочка:
phoenix> set | grep widgets
widgets