/home/caml-shaving

나의 Emacs 마크다운 링크 입력기

2022-04-07

태그: dev

마크다운으로 블로그 글을 정리하면서 자주 마주치는 일 중 하나는 링크를 입력하는 일이다. 마크다운 문법으로 링크는 다음과 같이 작성할 수 있다.

[Link Text](URL)

그런데 이 문법으로 내 블로그 안의 글을 링크할 때에는 보통 다음과 같은 규칙으로 만들고 있다.

[My Link Text](my-link-text)

즉,

… 을 URL로 사용하고 있다.

이게 한 두개면 그냥 그러려니 하고 직접 타이핑 하겠는데, 글을 정리하다보니 가짓수가 꽤 많은데다가, 링크 텍스트가 길어지면 URL을 쓰다가 오타가 나는 경우도 종종 있었다.

그래서 이걸 위한 나만의 마크다운 링크 입력 함수를 이맥스 리슾으로 작성하기로 했다. 오랜만에 블로그 제목다운 야크 낙타 털 깎기 작업이다.

만들려는 함수

이맥스에서 함수를 만드는 자세한 내용은 이맥스 가이드에 나와있다. 나는 다 읽지는 않았고 적당히 내가 필요한 부분만 골라 읽었다. 보아하니 (interactive) 옵션을 줘야 직접 호출할 수 있는 것 같다. 함수 이름은 적당히 fill-markdown-link-at-point 로 할 것이다. 함수 동작은 철저히 나의 유스케이스에 맞춰 작성할 것이다.

함수가 하는 일은 다음과 같다: 커서 근처에서 대괄호 짝을 찾은 다음, 대괄호 안의 모든 내용을 가져와서 (1) 전부 소문자로 만들고 (2) 알파벳 외의 글자는 -로 바꾼 다음 (3) 이 내용을 괄호로 감싸서 대괄호 바로 뒤에다 붙인다. 간단하지만 아무도 관심이 없어서 직접 만들게 되었다.

의식의 흐름으로 구현하기

내가 아는 마크다운 문법이 맞는지부터 확인해보자. 나는 보통 링크를 리스트로 정리하는데, 마크다운에는 다음과 같이 작업 리스트라는 확장 문법이 있다.

 - [ ] TODO
 - [x] DONE!

따라서, 정확한 구현은 [] 안의 문자열을 가져올 때 이게 작업 체크박스인지를 확인할 필요가 있다. 하지만 이건 내가 직접 해도 된다. 그러니 함수에서 이걸 체크하지 않고, 내가 직접 원하는 위치의 링크 텍스트 근처에서 함수를 실행하기로 했다.

이맥스의 내장 함수 목록을 살펴보니, 다음 함수를 사용하면 될 것 같다.

이 내장 함수들을 잘 조합해서 만든 함수는 다음과 같다.

(defun fill-markdown-link-at-point ()
  "FILL MARKDOWN LINK IN PARENTHESES FROM BRACKET."
  (interactive)
  (save-excursion
    (progn
      (let* ((start (search-backward "["))
             (end (search-forward "]"))
             (title (buffer-substring (+ start 1) (- end 1)))
             (title (replace-regexp-in-string "[^a-zA-Z0-9]" "-" title))
             (title (downcase title)))
        (goto-char (match-end 0))
        (insert (format "(%s)" title))
        ))))

원하는 함수를 만들었으니 이제 이 함수를 단축키에 등록하면 된다. 마침 마크다운 모드를 사용하고 있으므로 해당 모드에만 바인드 시킬 수 있다.

(use-package markdown-mode
  ....
  :bind
  ("C-c C-c C-l" . fill-markdown-link-at-point))

이제 마크다운 파일을 작성하다가 내가 원하는 형태의 링크를 추가하고 싶으면 단축키로 다음처럼 바로 실행할 수 있다.

fill-markdown


이렇게 내가 원하는 기능을 이맥스 리습으로 구현해보았다. 코드도 짧고 읽기 좋아서 만족한다. 이 마지막 구현을 얻기까지 (message)로 직접 출력도 해보고 (debug-on-entry)를 이용해서 스텝 바이 스텝으로 디버깅도 해보았다. 그러다가 이맥스 정규식 엔진으로는 대괄호 안의 문자열을 정확하게 못잡는다는 것을 직접 눈으로 확인하기도 했다. 여태껏 대부분 원하던 기능은 다 오픈소스로 공개된 패키지가 있어서 그것을 적절히 조합해서 사용해왔었는데, 이번에 딱 나만의 유스케이스가 생겨서 만들어 보았고 역시나 야크 털 깎기는 재밌었다. 다음에 또 이런 케이스가 있을지 모르겠지만 종종 나만의 함수를 만들어볼 기회가 있으면 좋겠다.


블로그 글을 쓰고 나서 직접 쓰다보니, 굳이 커서 위치를 원래 위치로 돌릴 필요가 없다고 느꼈다. 그래서 커서를 저장하지 않도록 수정했다.


[2022/09/05 수정] 계속 글을 쓰다보니 추가적인 반복 패턴을 발견할 수 있었다.

  1. 위의 기능을 통해 마크다운 문서 링크를 만든다.
  2. 문서 링크.md 파일을 만든다.
  3. 여기에 문서 제목, 문제 아웃링크, 기타 공통 필드를 채운다.

여기서 3번의 내용을 템플릿 파일로 만들고, 2번을 할 때 이 템플릿 파일을 링크.md로 복사해서 열도록 했다. Elisp에서는 모든 것이 버퍼에서 수행되는 덕분인지, 1번의 컨텍스트(링크 이름 등)를 그대로 복사한 파일에 적용할 수 있었다. 추가로 링크 파일로 이동할 때 마커 스택에 레퍼런스를 추가했더니 인덱스로 돌아오는 것도 한결 편해졌다.

최종 구현은 여기 에서 볼 수 있다. 참고로 기존에 유지하던 dotfiles에서 따로 .emacs.d로 이맥스 설정만 뺐다. 점점 최적화가 되어 가는 것 같아 뿌듯하다.