Dev_articles/General

[Hammerspoon] typora도 vim처럼 쓰고 싶다.. vim keymap 설정기!

humblEgo 2020. 12. 27. 02:37
hammerspoon을 통해 vim keymap을 설정한 과정을 공유합니다.

들어가며

vim에 편해지다 보면 딱히 vim을 쓸 때가 아니더라도 vim keymap을 쓰고 싶다는 생각이 들기 마련입니다. 귀찮게 마우스로 손을 옮길 필요 없이 키보드로 다 처리해버리는 게 생각보다 편하기 때문인데요, 이런 니즈를 공략하는 도구들이 이미 충분히 많습니다. 가령 제가 쓰고 있는 도구들만 해도 3개나 됩니다.

  • vimium : 크롬 브라우저 확장 앱입니다. 웨일 브라우저에서도 호환됩니다. 진짜 최고..
  • vimac : macOS 를 naviagate 할 수 있도록 도와줍니다.
  • VScodeVim: vscode의 vim emulator 입니다. vscode 상에서 쉽게 설치 가능합니다.

그런데 문득 typora나 티스토리 에디터로 글을 작성할 때도 vim처럼 hjkl로 방향키를 옮기고 싶다는 생각이 들었습니다. 마침 esc키에 한영변환 기능도 추가하며 썼던 hammerspoon을 이용하면 가볍게 구현해볼 수 있을 것 같습니다. 기능 이름은 vim_arrow_mode 정도면 좋겠네요!


목표

vim_arrow_mode 구현 목표는 아래 4가지를 잡았습니다.

  • 티스토리, typora 같은 문서 편집기에서도 vim처럼 hjkl로 커서를 이동시킬 수 있게 만들자.
  • 가능하다면 hjkl 뿐만 아니라 다른 기능도 맵핑시키자. 상태 보고 vim_arrow_mode에서 vim_mode 정도로 이름을 바꿔주자.
  • vim keymap 모드를 껐다가 킬 수 있어야 한다.
  • vim keymap 모드가 켜졌는지 여부를 쉽게 확인할 수 있어야 한다.

우선 hjkl 키를 방향키와 맵핑하자

키 맵핑 기본로직

hammerspoon은 lua 언어로 동작을 설정할 수 있습니다. harmmerspoon API 문서를 참고하여 `~/.hammerspooon/init.lua` 파일 안에 동작을 정의하면 됩니다. 저는"cmd + shift + space" 단축키로 vim_arrow_mode의 활성화/비활성화를 조절할 수 있도록 구현했습니다.

`hs.hotkey.new`로 hjkl 키를 각각 좌우상하 방향키와 맵핑시켰는데, `hs.hotkey.new(...):enable()`을 wrap 한 `hs.hotkey.bind`로도 대체 가능합니다.


키 반복 입력을 매끄럽게

실제로 사용해보니 키 맵핑은 잘 된 것 같은데, 키를 누르고 있을 때 반복 입력이 잘 안 되는 문제가 있었습니다. 커서를 오른쪽으로 빠르게 옮기려면 l키를 연타해야 하네요. l키를 누르고 있으면 커서가 오른쪽으로 스르륵 자연스럽게 움직이게 만들고 싶습니다.

hs.hotkey.new API 문서를 보니 키를 반복 입력할 때 호출하는 함수를 따로 4번째 인자로 넘겨줘야 하네요. 아래처럼 수정합시다.

repeatfn을 추가

이제 l키를 누르고 있으면 커서가 오른쪽으로 이동하지만, 딜레이가 좀 느껴집니다. 스택오버플로우를 보니 `hs.eventtap.keyStroke()`이 `newKeyEvent()`와 `hs.timer.usleep()`이 결합된 동작을 하기 때문이라고 합니다. hs.eventtap.keyStorke API 문서를 보니 세 번째 인자를 따로 넘겨주지 않으면 디폴트로 200ms 만큼 딜레이가 생긴다고 하네요. 아래처럼 아예 hs.eventtap.keyStorke의 딜레이를 30ms로 바꾼 `fastKeyStorke` 함수를 만들어서 해결했습니다.

keyStroke delay를 30ms로 설정


다른 키들도 매핑할 수 있을까

이제 hjkl키는 방향키로 잘 작동합니다. 그런데 뭔가 아쉽네요. 사실 vim의 강력함은 hjkl 뿐만 아니라 강력한 문서 편집 기능들을 단축키로 빠르게 쓸 수 있다는 점에서 오는 것 아니겠어요? vim과 완전히 동일하진 않더라도 자주 쓰는 단축키들이 vim과 비슷하게 동작하도록 추가로 키 맵핑을 하면 정말 도움될 것 같습니다.

다행히 제가 자주 쓰는 기능들은 얼추 hammerspoon으로 구현할 수 있었습니다. 가령 y y를 연달아 누른 경우 "cmd + left", "shift + cmd + right", "cmd + c"를 순차적으로 eventtap 하도록 만드는 식입니다.

vim_keymap 구현예시- 'y y'

키 맵핑할 때 반복해서 입력해줘야 하는 modifier들은 table로 묶어서 관리하니까 깔끔해지네요. 

y 나 d처럼 modifier가 아닌데 modifier처럼 써야 하는 경우엔 `hs.hotkey.modal.new()`를 활용해서 해결했습니다. 이후 `y_with_other_bind`는 `set_vim_keymap`과 `unset_vim_keymap`에서 enable, disable 처리됩니다.

한편 x나 d 키와 연관된 기능들은 엄밀히 말해서 '삭제'가 아니라 '잘라내기'이지만, 개인적으로 사용하기에 무방하다는 판단 하에 전자로 구현했습니다.

결과적으로 hjkl 외에 아래 키들을 추가로 맵핑했습니다. 이제 기능 이름을 vim_arrow_mode에서 vim_mode로 바꿔줘야겠네요!

  • x : 글자 삭제
  • w : 다음 단어로 이동
  • b : 이전 단어로 이동
  • i : 현재 커서 위치에서 insert 모드로 진입
  • a : 현재 커서의 바로 오른쪽 한 칸 위치에서 insert 모드로 진입
  • o : 현재 커서의 바로 아랫 줄에 insert 모드로 진입
  • p : 복사한 내용 붙여 넣기
  • u : 실행 취소
  • shift + d : 현재 라인의 커서 오른쪽 내용을 모두 삭제
  • shift + a : 현재 라인의 오른쪽 끝에서 insert 모드로 진입
  • shift + o : 현재 커서의 바로 윗 줄에 insert 모드로 진입
  • y + y : 현재 라인 복사
  • d + d : 현재 라인 삭제
  • d + w : 커서 기준 다음 단어 삭제
  • d + b : 커서 기준 이전 단어 삭제

vim icon을 메뉴바에 표시해보자

실사용해보니 편합니다. 편하긴 편한데.. 엄.. vim mode가 켜져 있는데 타자를 치거나 vim mode가 켜져있지 않은데 hjkl로 이동하려고 하는 경우가 자꾸 생기는 게 아쉽습니다. vim mode가 켜져 있는지 여부를 쉽게 확인할 수 있도록 만들면 개선될 것 같네요.

메뉴바에 아이콘을 추가해보자

`hs.menubar.new()`를 이용해서 macOS 상단 메뉴바에 아이콘을 만들 수 있습니다. `setClickCallBack` 을 이용해서 아이콘을 클릭 시 `set_vim_mode` 함수가 실행되도록 설정해줄 수도 있습니다. 결과물은 아래와 같습니다. 쓸만하네요 :)

우측 상단 메뉴바의 아이콘이 바뀝니다


내친김에 모듈화도?

길~어진 init.lua

여기까지 만들다 보니 `init.lua`에 포함된 코드가 상당히 길어졌습니다. 이렇게 되면 가독성이 떨어지거니와 서로 다른 기능 간에도 네임스페이스가 겹칠 가능성이 커지는 것도 문제입니다. 유지보수가 어려워지겠네요;

코드가 길어진 것은 vim keymap에 포함된 key가 많은 것도 이유지만, 보다 근본적인 이유는 hammerspoon에 기능을 추가할 때마다 init.lua에 코드를 몰아넣었기 때문입니다. 서로 다른 기능들을 모듈화 시키면 문제가 많이 해소될 것 같습니다.

lua 15분 안에 배우기Hammerspoon 사용하기를 보니 어떻게 구현해야 할지 감이 팍 옵니다.

vim_keymap_obj 에 기능을 담아서 리턴

우선 vim_keymap 기능을 따로 파일로 빼내고, 해당 기능을 담은 객체(엄밀히 말해 lua table)를 리턴하도록 수정해줍니다. 다른 기능들도 마찬가지로 파일로 나눠줍니다.

.
├── Spoons
├── init.lua
└── modules
    ├── change_input_source_if_ko.lua		  # 선택한 키에 인풋소스 변환기능과 escape기능을 맵핑하는 모듈
    ├── extra_shortcuts.lua			  # 자주 쓰는 어플리케이션 단축키모듈 (ex slack) 
    └── vim_keymap.lua				  # 키보드 키맵을 vim_keymap으로 바꿔주는 모듈

2 directories, 4 files

저는 결과적으로 디렉토리 트리를 위처럼 구성했습니다.

각 모듈을 불러오는 int.lua

이제 `init.lua`에서 각 모듈을 불러온 뒤 함수를 실행해주게끔 수정하면 됩니다.


마치며

Hammerspoon을 가지고 노는 게 재밌어서 시간 가는 줄 몰랐습니다. 잘 응용하면 앞으로 생산성을 높이는데 도움될 것 같아요. 제가 구현한 vim과 관련된 hammerspoon 설정 파일은 vimlife_with_hammerspoon 깃헙레포에서 확인할 수 있습니다. 누구 보이기 부끄러운 코드지만 개인적으로 쓰기에는 충분한 것 같습니다. 저처럼 vim을 좋아하는 분에게 도움되길 바랍니다.


참고