Zsh Vi 맵핑에서 백스페이스 고치기

이 글에서는 Zsh의 Vi 맵핑에 대해 알아보고, Vi 맵핑에서 나타나는 백스페이스가 안 먹는 문제의 해결법을 알아본다. Vi의 기본적인 사용법을 알고 텍스트를 편집할 줄 안다고 가정한다.

셸의 Vi 맵핑

일반적으로 셸의 기본 키맵핑은 메모장 매핑으로 설정되어있다. 명령어를 입력하다가 중간에 내용을 고치고 싶으면 좌우 방향키로 커서를 움직일 수 있다. 또, 상하 방향키로는 이전에 입력했던 명령어들을 들여다볼 수 있다. 그러다 엔터를 눌러 실행.

한편 bashzsh 등의 셸에서는 Vi와 비슷한 키맵핑을 할 수 있다. bash의 경우 set -o vi, zsh의 경우 bindkey -v 명령어로 맵핑을 전환할 수 있다. 이 명령어를 각각 ~/.bashrc, ~/.zshrc에 넣어 기본 설정이 되도록 할 수도 있다.

Vi 키맵핑이란 이런 것이다.

Zsh Vi 맵핑의 백스페이스 문제

문제라면 문제인 점은 Zsh의 Vi 맵핑이 Vi의 동작을 너무 충실하게 구현했다는 점이다. Vi는 Vim과는 다른 프로그램이다. 도움말에 따르면 Vim stands for Vi IMproved라고 한다. 즉 Vim이라는 이름 자체가 Vi의 개선판이라는 뜻.

때문에 어떤 것이 Vi의 동작을 충실히 구현했다라고 한다면 그 말은 두가지 방식으로 두려울 수 있다. 첫번째 경우는 Vim의 동작을 따르면서 그 연장자인 Vi의 권위를 빌리고 있다고 말하는 허영적 수사법이 두려운 것이고, 두번째 경우는 정말로 Vi의 동작을 따르고 있으므로 온갖 이해하기 어려운 호환적(compatible)인 동작을 보여서 두려운 것이다. 호환적… 그것은 옛 방식에 집착하는 과거의 망령 같은 것이고 어떨 때는 호환마마처럼 두려운 것이다….

어쨌든 조금 더 무섭게도 zsh은 두번째 방식으로 두렵다. 즉 옛 Vi의 동작을 충실히 구현한다. 구체적으로는 백스페이스에 있어서 Vi의 기본 동작을 채택했다. 이를테면, 명령어를 쓰다가 중간에 인자 하나를 지우고 싶어도 백스페이스로는 지울 수가 없다. NORMAL 모드에서 x(한글자 지우기) 또는 dw(한 단어 지우기) 등을 써야하는듯한데 메모장이 익숙한 우리에게는 너무나도 낯선 방식이다. 심지어는 Vim에 익숙해진 내게도 꽤 낯설다.

이 기묘한 설정의 기원은 Vim 도움말을 읽어보면 찾을 수 있다. Vim의 'backspace' 옵션은 INSERT 모드에서 백스페이스가 어떤 동작을 하게 할지 설정한다. 도움말을 읽어보면 'backspace'에 설정할 수 있는 값들은 이렇다:

indent
자동 들여쓰기를 지울 수 있다.
eol
줄바꿈 문자를 지울 수 있다 ― 두 줄을 합칠 수 있다.
start
INSERT 모드를 시작한 지점 앞의 문자들도 지울 수 있다.

(그 외에 nostop라는 것도 있는데 start와 대충 비슷하다.)

이 옵션의 기본값은 Vi에서는 "", Vim에서는 "indent,eol,start"다. 그리고 바로 이 기본값이 Vi의 기묘한 동작을 설명해준다.

start가 커서 앞의 글자들을 지울 수 있게 해준다는 건 곧 start 없이는 커서 앞의 글자들을 지울 수가 없다는 이야기이다. Vim에는 start가 설정되어 있고 Vi에는 설정되어있지 않다. 그 말인즉슨 Vi에서는 백스페이스가 지우지 못하는 글자들이 너무 많다는 것이다….

그리고 Zsh은 이 점을 그대로 계승한다. Zsh에서 Vi 맵핑을 사용하면 사용자는 Vi에서처럼 두가지 키맵을 오가게 된다. 두 키맵이란 viins와 vicmd로, 각각 INSERT 모드와 NORMAL 모드에 대응된다. man zshzle에 따르면, viins 키맵에서 백스페이스(^H)는 기본적으로 vi-backward-delete-char라는 동작으로 매핑되어 있다. 그 동작의 설명은 이렇다:

커서 앞의 글자를 지운다. 줄바꿈은 지우지 않는다. INSERT 모드에서 사용하면, 처음 INSERT 모드로 들어갔던 위치 이전의 글자는 지우지 않는다.

즉 문제의 Vi다운 동작을 충실히 구현하고 있는 것이다.

해결법

이 문제는 백스페이스가 할 동작을 직접 지정해줌으로써 해결할 수 있다:

bindkey -V ^H backward-delete-char

man zshzle에 따르면 bindkey는 Zsh의 내장 명령어로, 키맵을 수정한다. bindkey -V KEY WIDGET과 같이 사용하면 KEY를 눌렀을 때 WIDGET을 실행하도록 할 수 있다. ^H캐럿 표기법으로 ASCII 0x08 문자, 즉 백스페이스를 나타낸다. backward-delete-char의 동작은 별로 놀랍지 않게도 커서 앞의 글자를 지운다라고 한다. 즉 백스페이스를 누르면 커서 앞의 무슨 글자든 지운다는…, 이제는 당연해진 동작을 하게 되는 것이다.

또 참고로, 놀랍지 않게도, Zsh의 기본 키맵인 emacs에서는 ^H가 이미 backward-delete-char에 매핑되어있다….


이번 글에서는 Zsh Vi 맵핑에서 백스페이스가 안 먹는 문제를 해결해보았다. 곁다리로 Vim과 Zsh의 속살도 들여다 보았다. 종종 매뉴얼을 읽는 일은 재미있다. 이런 동작들이 설정된 역사도 알아보면 재밌겠지만 그랬다가는 시간 가난뱅이가 될 것이기에 일단 미뤄두기로 한다.

참고문헌

작성:

메뉴로 가기