ㅈㅅㄹ

그냥 전부 다 line-feed[각주:1]로 통일 하지 왜 굳이 EOL(End of line)을 OS 마다 다르게 하는 지 모르겠지만, 어쨌건 Windows에서는 line feed앞에 carriage return[각주:2]을 넣어서 라인을 구분하게 했고, Mac에서는 또 carriage-return만으로 구분하도록 했다.[각주:3] 사실 이건 그냥 해당 OS에서만 편집을 하고 사용하게 되면 문제가 없긴 하지만, 편집한 파일을 다른 OS에서 열어본다거나(다행히 Emacs에서 열어본다면 별 문제 없겠지만) 혹은 tramp를 통해 remote에서 스크립트 파일 같은 걸 작성하다보면 문제가 되는 경우가 종종 발생한다.


Emacs에서도 VIM 처럼 EOL 타입을 지정해 줄 수 있긴 하지만, VIM의 fileformat처럼 EOL 타입을 위한 전용 변수가 있어서 여기다 dosunix를 지정하는 것으로 모드 전환이 되는 것이 아니라, 현재 버퍼의 encoding type에 suffix로 지정을 해 주는 방식을 사용한다.C-h vM-x describe-variable RET을 통해 buffer-file-coding-system의 값을 살펴 보면 현재 버퍼에 지정된 encoding type과 EOL 모드를 알 수 있다. 가령 내 경우 윈도우에서 일반적인 버퍼에 korean-iso-8bit-dos가 변수의 값으로 지정되어 있는데, 여기서 앞부분이 coding system, 즉 버퍼의 encoding 형식을 지정하는 것이고 마지막 -dos 부분이 EOL 타입을 의미하는 것으로, 만약 encoding은 그대로 두고 EOL 타입을 unix 스타일로 바꾸고 싶다면 korean-iso-8bit-unix를 지정해 주면 되는 것이다.


그러나 이게 상당히 귀찮다는 게 문제이다. 단순히 EOL 타입만 변경하고 싶어도 C-h v buffer-file-coding-system RET을 입력 해서 현재 변수 값을 살펴 보고, 가령 현재 coding system이 korean-iso-8bit라는 걸 확인한 다음, M-: (setq buffer-file-coding-system 'korean-iso-8bit-unix) RET을 입력해줘야 한다. 생각만해도 귀찮음이 몰려 오지 않나? 이게 만약 파일 하나가 아니라면...


없으면 만들면 되지. 아래 소스 처럼 cheol이라는 interactive function을 정의했다. 현재 버퍼의 coding system은 그대로 유지하고 EOL 설정만을 unix, dos, mac 중 입력된 값에 따라 바꿔 준다.

(defun my:read-eol-type ()
  (let* ((os (cond
              ((eq system-type 'ms-dos) "dos")
              ((eq system-type 'windows-nt) "dos")
              ((eq system-type 'darwin) "mac")
              (t "unix")))
         (cur-eol (or (with-current-buffer (current-buffer)
                        (let ((cs (symbol-name buffer-file-coding-system)))
                          (when (string-match "\\(.+\\)-\\(unix\\|dos\\|mac\\)\\'" cs)
                            (replace-match "\\2" t nil cs))))
                      os))
         (readfunc (if (and (boundp 'ido-mode) ido-mode)
                       #'ido-completing-read
                     #'completing-read)))
    (values (apply readfunc `(,(format "EOL Type (current: %s): " cur-eol) ("unix" "dos" "mac") nil t
                              "" nil ,os)))))

(defun cheol (eol-type)
  "Change buffer's EOL to preferred one in given OS"
  (interactive (my:read-eol-type))
  (let ((cs (format "%s-%s" (with-current-buffer (current-buffer)
                              (let ((cs (symbol-name buffer-file-coding-system)))
                                (if (string-match "\\(.+\\)\\(-unix\\|-dos\\|-mac\\)\\'" cs)
                                    (replace-match "\\1" t nil cs) cs)))
                    eol-type)))
    (unless (member (list cs) coding-system-alist)
      (error "Unsupported coding system: %S" cs))
    (setq buffer-file-coding-system (intern cs))
    (set-buffer-modified-p t)))

my:read-eol-type 함수는 입력의 자동 완성을 위해 작성한 것으로, 입력을 간단하게 하는 역할도 있지만 cheol 함수의 eol-type 인자가 반드시 unix, dos, mac 중 하나로 들어오도록 제한하는 역할도 한다.[각주:4]



실행해서 EOL 타입을 변경해 보면 mode-line에 (Unix) 등으로 현재 OS의 기본과 동일하지 않을 경우 지정된 EOL 타입이 표시되고, VIM처럼 격렬한 ^M의 향연이 펼쳐지는 대신 모든 라인에 변경된 정책을 적용해버린다. 그러나 이러한 변경은 버퍼에만 해당 되는 것으로 실제 파일에는 적용되지 않았기 때문에 결국 저장을 해야 하는데, 단순히 buffer-file-coding-system을 변경한 것은 버퍼 내용을 건드린 것이 아니라 modified flag가 켜지지 않는다. 기껏 EOL 타입 바꿔 놓고 저장이 안되는 상황이 벌어질 수도 있는 것이다. 이것이 cheol 함수 마지막에 (set-buffer-modified-p t)를 집어 넣은 이유로, EOL 타입 변경 자체도 변경점으로 보고, 관련한 변경 사항이 유실되지 않도록 처리했다.


이제 EOL 타입 변경이 필요할 때, 가령 unix 타입으로 변경한다면 M-x cheol RET u TAB RET과 같이 입력 해주면 되니 한결 편해졌다. 키 바인딩까지 추가하면 더 쉽게 변경이 가능하겠지만 EOL 타입 변경이 키 바인딩을 굳이 지정할만큼 자주 쓰이는 것도 아니고, 뭐 이 정도면 쓰기에 충분할 것 같다. 아쉬운 점은 Mac의 경우 버전 9까지만 mac 타입을, 이후로는 unix를 OS 기본 타입으로 설정하고 싶지만 현재로써는 system-typedarwin 값 외에는 딱히 참조 할만한 값이 없어서 그냥 다 mac 타입으로 지정해 놓고 있는 것이긴 한데... 어차피 요즘은 다 10 쓰니까 그냥 darwin인지 체크하는 라인은 삭제하는 게 나을지도 모르겠다. 뭐 난 맥 안쓰니까 상관 없을 듯 -_-

  1. ASCII Code 0x0A, escape sequence로는 보통 '\\n'으로 표기함 [본문으로]
  2. ASCII Code 0x0D, escape sequence로는 보통 '\\r'로 표기함 [본문으로]
  3. Mac OS X 9까지는 이렇고, 10부터는 unix처럼 line-feed만으로 EOL을 표기하고 있다 [본문으로]
  4. completing-readrequire-match 인자를 통해 지정된 값들 이외의 것이 입력될 경우 입력이 완료되지 못하게 한다. 자세한 사항은 Emacs Lisp Manual의 19.6.2 Completion and the Minibuffer를 참조하기 바란다. [본문으로]