Emacs의 버퍼 변경 알림 프롬프트에서 변경 내용 표시하기
이맥스를 사용하면 할 수록 이맥스 밖으로 나가서 뭔가 하는 일이 거의 사라지기 때문에, 회사에서 이맥스 한 번 띄워 놓으면 보통은 일주일 넘게 세션을 끄지 않게 마련이고, 그러다 보면 나중에는 엄청나게 많은 버퍼가 열려 있게 된다. 이런 상황에서 가끔 뭔가 버퍼에 수정을 해 놓긴 했는데 저장하지 않은 상태로 내버려 두거나 혹은 키 입력이 버퍼에 잘 못 들어가서 이상한 문자가 입력되어 있거나 하는 이유로, Magit 같은 걸 실행해서 save-some-files이 호출 될 때나 혹은 C-x k로 kill-buffer 할 때 니가 뭔가 바꿨으니 저장하겠냐고 물어보지만 사실 내가 뭘 바꿨는지 모르기 때문에 -_- C-g로 취소 후 실제 변경점을 찾아 보는 일련의 행위가 필요하게 된다.
문제는 그게 상당히 귀찮다는 점이다. 어쩌다 타이핑 미스로 한 두 글자 들어간 버퍼들 때문에 일일히 버퍼들 띄우는 것도 귀찮지만, save-some-files의 프롬프트에서 C-r을 눌러 해당 버퍼를 볼 수 있다 하더라도 실제 변경점이 표시되는 건 아니기 때문에 결국 버퍼에 가서 M-x undo RET를 해 보거나 M-x diff-buffer-with-file RET 해서 변경점을 보는 수 밖에 없다. 이런 변경점을 프롬프트 띄우면서 보여주면서 저장을 할 건지 그냥 진행할 건지를 선택할 수 있게 하면 좋지 않을까.
우선 해당 버퍼와 버퍼의 변경점을 보여 주기 위한 함수를 만든다.
(defun my:display-buffer-modification (&optional buffer-or-name)
(let* ((buffer (or (and buffer-or-name (get-buffer buffer-or-name))
(current-buffer)))
(diff-switches "-urN")
(file-name (buffer-file-name buffer)))
(display-buffer buffer '(display-buffer-same-window))
(delete-other-windows)
(diff (if (file-exists-p file-name) file-name null-device) buffer nil 'noasync)))
이 함수를 실행하면 아래의 그림 처럼 왼쪽에 원래 버퍼를, 오른쪽에 변경점을 보여주게 된다. 사실 어떻게 보여 주는 게 좋을 것인가 하는 문제는 취향의 영역이라 다른 모양을 원한다면 취향에 맞춰서 변경하면 되겠다.
이 함수를 호출하도록 kill-buffer의 advice function을 정의하면,
(defadvice kill-buffer (around my:kill-buffer-modified (&optional buffer-or-name))
"Adviced kill buffer to show diff with original file to verify the changes."
(with-current-buffer (or buffer-or-name (current-buffer))
(if (and (buffer-live-p (current-buffer))
(buffer-modified-p)
(buffer-file-name))
(save-window-excursion
(my:display-buffer-modification (current-buffer))
(when (yes-or-no-p (format "Buffer %s modified; kill anyway? " (buffer-name)))
(set-buffer-modified-p nil)
ad-do-it))
ad-do-it)))
(ad-activate 'kill-buffer)
버퍼가 수정되어 있으면 my:display-buffer-modification을 호출하고 프롬프트를 띄운다. 사용자가 yes를 입력할 경우 set-buffer-modified-p로 modification flag를 제거해 주어 다시 kill-buffer에서 프롬프트를 띄우지 않도록 하면 된다. my:display-buffer-modification에서 window configuration을 바꾸기 때문에 프롬프트 이후 원래 configuration으로 복구하도록 save-window-excursion을 걸어 주었다.
save-some-files의 경우는 내부에서 loop를 돌리고 마땅히 hook을 걸 만한 부분도 없기 때문에 advice function을 정의하기엔 난감한 부분이 있어 기존 함수를 가져와서 재정의 하도록 했다. 결국 함수 정의를 다시 덮어 쓰는 것이므로 새로 정의한 my:save-some-buffers 함수가 동작하도록 만들려면 일단 files.el이 load된 이후에 재정의 하도록 해야한다. eval-after-load 내부에서 내가 새로 정의한 함수로 alias를 걸도록 해 주었다.
;; override default save-some-buffers (from 24.5.5 files.el)
(defun my:save-some-buffers (&optional arg pred)
"Save some modified file-visiting buffers. Asks user about each one.
You can answer `y' to save, `n' not to save, `C-r' to look at the
buffer in question with `view-buffer' before deciding or `d' to
view the differences using `diff-buffer-with-file'.
This command first saves any buffers where `buffer-save-without-query' is
non-nil, without asking.
Optional argument (the prefix) non-nil means save all with no questions.
Optional second argument PRED determines which buffers are considered:
If PRED is nil, all the file-visiting buffers are considered.
If PRED is t, then certain non-file buffers will also be considered.
If PRED is a zero-argument function, it indicates for each buffer whether
to consider it or not when called with that buffer current.
See `save-some-buffers-action-alist' if you want to
change the additional actions you can take on files."
(interactive "P")
(save-window-excursion
(let* (queried autosaved-buffers
files-done abbrevs-done)
(dolist (buffer (buffer-list))
;; First save any buffers that we're supposed to save unconditionally.
;; That way the following code won't ask about them.
(with-current-buffer buffer
(when (and buffer-save-without-query (buffer-modified-p))
(push (buffer-name) autosaved-buffers)
(save-buffer))))
;; Ask about those buffers that merit it,
;; and record the number thus saved.
(setq files-done
(map-y-or-n-p
(lambda (buffer)
;; Note that killing some buffers may kill others via
;; hooks (e.g. Rmail and its viewing buffer).
(and (buffer-live-p buffer)
(buffer-modified-p buffer)
(not (buffer-base-buffer buffer))
(or
(buffer-file-name buffer)
(and pred
(progn
(set-buffer buffer)
(and buffer-offer-save (> (buffer-size) 0)))))
(or (not (functionp pred))
(with-current-buffer buffer (funcall pred)))
(if arg
t
(setq queried t)
(if (buffer-file-name buffer)
(progn (my:display-buffer-modification buffer)
(format "Save file %s? "
(buffer-file-name buffer)))
(format "Save buffer %s? "
(buffer-name buffer))))))
(lambda (buffer)
(with-current-buffer buffer
(save-buffer)))
(buffer-list)
'("buffer" "buffers" "save")
save-some-buffers-action-alist))
;; Maybe to save abbrevs, and record whether
;; we either saved them or asked to.
(and save-abbrevs abbrevs-changed
(progn
(if (or arg
(eq save-abbrevs 'silently)
(y-or-n-p (format "Save abbrevs in %s? " abbrev-file-name)))
(write-abbrev-file nil))
;; Don't keep bothering user if he says no.
(setq abbrevs-changed nil)
(setq abbrevs-done t)))
(or queried (> files-done 0) abbrevs-done
(cond
((null autosaved-buffers)
(message "(No files need saving)"))
((= (length autosaved-buffers) 1)
(message "(Saved %s)" (car autosaved-buffers)))
(t
(message "(Saved %d files: %s)"
(length autosaved-buffers)
(mapconcat 'identity autosaved-buffers ", "))))))))
(eval-after-load 'files
`(defalias 'save-some-buffers 'my:save-some-buffers))
이제 수정 내용을 저장할꺼냐고 물어보는 프롬프트와 동시에 저장되지 않은 수정 내용이 떠 주니 한결 편하다. 어쩌다 오타로 들어간 것들은 바로바로 무시해 줄 수 있고. 쓰다 보면 my:display-buffer-modification을 쓸 만한 다른 케이스들도 나올 것 같긴 한데, 일단은 이 두가지만 해줘도 아주 좋은 듯.
'Emacs' 카테고리의 다른 글
| Emacs 읽기 전용 버퍼에서 모드 키 조합 없이 이동하기 (0) | 2016.04.07 |
|---|---|
| Emacs의 자동 저장 및 복구 기능을 좀 더 잘 활용하자 (0) | 2016.04.04 |
| Emacs spell checker (0) | 2016.03.11 |
| Emacs의 ansi color 설정 (0) | 2016.03.07 |
| auto-complete-c-headers 설정 (1) | 2016.03.03 |