kill-current-sexp의 Emacs, VSCode 구현
나만 쓰는 명령어 kill-current-sexp
kill-current-sexp은 나만 쓰는 명령으로, 말 그대로 현재 커서가 있는 S-expression을 kill(잘라내기)하는 명령이다.
문제는 몇몇 코너 케이스인데, 커서가 붉은색에 위치할 때, kill-current-sexp의 결과를 나타내면 다음과 같다.
(s (expre (ssion)) sexp) => (s (expre (ssion)) sexp) => (s ((ssion)) sexp) (s (expre (ssion)) sexp) => (s (expre (ssion)) sexp) => (s sexp) (s (expre (ssion)) sexp) => (s (expre (ssion)) sexp) => (s sexp) (s (expre (ssion)) sexp) => (s (expre (ssion) sexp)) =>
그런데 vim과 일반적인 에디터의 커서 표시 차이 때문에, Paredit에는 kill-current-sexp과 같은 명령이 존재하지 않는다.
왜 Paredit에는 없나?
Emacs나 VScode에서 Vim바인딩의 커서는 실제 커서의 오른쪽에 있는 문자에 음영을 입혀서 구현한다. 그러니까 위 예시들에서 실제 에디터의 커서 위치는 다음과 같다.
(s (expre (ssion)) sexp) = (s (|expre (ssion)) sexp) (s (expre (ssion)) sexp) = (s |(expre (ssion)) sexp) (s (expre (ssion)) sexp) = (s (expre (ssion)|) sexp) (s (expre (ssion)) sexp) = (s (expre (ssion) sexp)|)
Paredit은 vim처럼 커서가 문자를 가리키는 게 아니라 "문자의 사이"를 가리키는 일반적인 에디터를 가정하고 만들어졌다. 그래서 "현재의 sexp"은 보통 커서의 뒤에 등장하는 sexp을 의미하며, "현재의 sexp을 삭제한다"고 가정하면 다음과 같은 결과가 나온다. (3,4번째 결과에 주목)
(s (|expre (ssion)) sexp) => (s (|expre (ssion)) sexp) => (s ((ssion)) sexp) (s |(expre (ssion)) sexp) => (s |(expre (ssion)) sexp) => (s sexp) (s (expre (ssion)|) sexp) => (s (expre (ssion)|) sexp) => (s (expre (ssion)) sexp) (s (expre (ssion)) sexp|) => (s (expre (ssion) sexp)|) => (s (expre (ssion)) sexp)
꼬우면... 아시죠?
하지만 나 같은 vim틀딱에게 kill-current-sexp은 매우 편리한 기능이다. 써보면 안다... 그래서 내가 직접 만들었다.
구현 자체는 코너 케이스만 잘 처리하면 된다.
- 커서를 한 칸 앞으로 옮기고
- 커서를 바로 이전 sexp으로 가거나 상위 sexp으로 올라가게 한 후
- 현재 커서에서 앞쪽을 kill하면 된다.
1,2를 거치면 커서는 항상 sexp의 첫번째 문자에 위치하게 된다.
(defun kill-current-sexp () (interactive)
(forward-char) (paredit-backward) (kill-sexp))
(evil-define-key 'normal clojure-mode-map
(kbd "X") 'kill-current-sexp)
[VSCode의 settings.json]
// Vim Keybindings
"vim.normalModeKeyBindings": [
// X: Kill current sexp
{ "before": ["X"],
"commands": [
{"command":"cursorMove", "args": {"to": "right"}}, // [a,b]
"paredit.backwardSexpOrUp",
"paredit.killSexpForward"]},
],
// Use killing as cutting like emacs
"vim.useSystemClipboard": true, // [c]
"calva.paredit.killAlsoCutsToClipboard": true,
단축키는 간단히 vim 노말 모드에서 대문자 X를 쓰도록 맵핑했다. commands에 명령을 하나씩 넣고 저장한 후 X를 눌러 작동을 확인하는 걸 반복하는 방식으로 코딩했다.
맺음말
이게 얼마나 편한지는 써 본 사람만 안다 진짜 ㄹㅇ루다가
다만 Vim 바인딩과 Lisp과 Paredit을 모두 쓰는 변태한테만 의미 있다는 게.. 좀 심하게 매니악하긴 하다.
아무튼 난 잘 쓰고 있다. 님들도 쓰쉴?
2022.06.17 업데이트
위 기능을 쓰다 보면 가끔씩 이런 창이 뜨는데 기능에는 아무 문제가 없다. 왜 뜨는 거지? 어떻게 하면 없앨 수 있지 이거?
또 다른 심하게 매니악한 설정: 한자키 -> ESC
참조한 문서
[a] https://code.visualstudio.com/api/references/commands#:~:text=an issue on-,cursorMove,-- Move cursor to
[b] https://github.com/VSCodeVim/Vim#key-remapping
[c] https://github.com/VSCodeVim/Vim#quick-example
[c] https://github.com/VSCodeVim/Vim