ㅈㅅㄹ

Emacsclient의 활용

Emacs2016. 3. 2. 23:58

사실 본격적으로 Emacs를 사용하게 되면 Emacs 바깥에서 무언가를 잘 하지 않기 때문에 점점 필요성을 못 느끼게 되긴 하지만, Emacs를 처음 사용할 때는 일반적인 에디터의 활용법에서 크게 벗어나지 못하게 마련이고 그건 나도 예외가 아니었다. 무슨 말인가 하면 터미널에서 작업을 하다가 무언가 수정할 일이 있을 때 에디터를 사용하게 되는 그 익숙해진 사용 틀에서 벗어나기가 쉽지 않고, 그런 터미널 환경 기반의 사용 틀에 익숙하던 사용자가 Emacs를 처음 쓰게 되면서 느끼는 문제는, 뭔놈의 에디터 하나 띄우는 데 너무 실행 시간이 길다는 점이다.


어지간히 준수한 사양의 컴퓨터에서도 이것 저것 .emacs.d 아래에 ELISP 파일이 쌓일만큼 쌓인 사람들의 Emacs 실행 시간은 못해도 5초 정도는 들게 마련이고, 사양이 떨어지고 I/O 시간이 길어 질 수록 그 시간은 꽤나 늘어나게 되는데, 이건 단순히 git commit 하나 올릴려고 5내지 10초는 기다려야 하는 웃지 못할 상황이 벌어지게 된다. 그래서 어떤 사람은 이런 상황에 맞춰 최소 필수 패키지만 로딩하는 lite 버전을 실행하게 command-line option을 파싱해서 분기를 타도록 Emacs 설정을 구성하거나 하는데, 이 보다는 아무래도 emacsclient를 잘 활용하는 편이 여러모로 손쉽고 활용 면에서도 유리하다 생각한다.


다만 emacsclient를 사용하기 위해서는 서버가 실행되고 있어야 하는데, 일반적인 에디터 세션에서 서버를 구동하거나 혹은 백그라운드 데몬 형태로 실행할 수 있다. 아쉽게도 Windows 환경에서는 데몬으로 실행은 불가능하고 어쨌든 Emacs 세션이 하나는 떠 있어야 하지만, POSIX 환경에서는 백그라운드로 데몬을 띄워 놓고 일반 노트패드 띄우듯이 emacsclient를 실행하는 식의 사용이 충분히 가능하며, 당연하겠지만 같은 세션을 공유하고 있기 때문에 이미 열려 있는 버퍼들을 전부 공유할 수 있다. 맥에서는 데몬으로 실행이 되려나? 맥 쓰던 시절엔 Emacs를 써 본 적이 없어서 모르겠네.


Emacs를 데몬으로 실행하기 위해선 그냥 아래의 명령어를 실행하면 된다.

$ emacs --deamon

그리고 혹시나 이미 떠 있는 Emacs 데몬을 정상적인 절차로 종료를 하려면 아래의 명령어를 주고 실행하면 된다

$ emacsclient -e "(kill-emacs)"

좀 더 잘 활용을 하는 방법에 대해서는 https://www.emacswiki.org/emacs/EmacsAsDaemon에 잘 설명이 되어 있으니 참조하면 되겠다. 사실 내 경우 데몬으로 사용하는 방법은 나와는 잘 맞지 않기 때문에 쓰지 않는터라 여기 이상 설명 할 건덕지가 없기도 하고...


내 경우는 이 방법을 사용하는 대신 간단한 스크립트를 통해 최초에 Emacs를 띄울 때 Emacs 서버를 구동하고, 이미 서버가 띄워져 있으면 emacclient를 호출하는 방식을 사용한다. 우선 이 방법을 사용하려면 직접 M-x server-start를 실행하거나 Emacs 설정 서버를 실행하도록 추가 해 주어야 한다. 아래는 Emacs 실행 시 서버가 실행 중이 아니라면 자동으로 구동하도록 하는 설정이다.

(ignore-errors
  (let ((warning-minimum-level :emergency)) ; a kinda tricky way to suppress warning
    (require 'server)
    (unless (server-running-p) (server-start)))) ; start server

사실 다른 부분은 이맥스가 뜰 때 혹시나 귀찮은 에러나 경고 메시지가 보기 싫어서 넣은 것이고 핵심은 (server-running-p)가 아닐 경우 (start-server)를 한다는 게 핵심이다. 다만 (server-running-p)가 autoload function이 아니기 때문에 먼저 (require 'server)server.el ELISP 라이브러리를 로드하는 것까지가 꼭 필요한 부분인 거고.


어쨌건 데몬이나 서버가 실행 되었다면 간단히 특정 파일을 편집하기 위해선 마치 다른 에디터를 사용하는 것처럼 emacsclient를 실행하면 된다.

$ emacsclient aa.txt

추가로 emacsclient의 경우 server에 접속하는 데 실패 할 경우 fallback으로 다른 에디터를 실행하도록 하는 옵션을 제공하는데, 아래는 서버가 떠 있으면 emacsclient를 실행해서 기존 서버에 접속하고, 그렇지 않을 경우 아예 Emacs를 띄우도록 하는 명령어이다.

$ emacsclient -a emacs aa.txt

이제 이걸 쉘 스크립트로 구성해 보자.

#!/bin/sh
exec emacsclient -a emacs "$@" 2> /dev/null

이 스크립트를 가령 $HOME/bin/edit 같은 걸로 저장을 해 두고 $EDITOR 환경 변수나 git-config에 지정해 두고 쓰면 emacsclient를 사용할 수 있을 경우 그걸 사용하게 된다. 물론 안되면 emacs 띄우는 거고...


하지만 emacsclient의 기본 옵션만으로는 여러가지 상황에 대응하기는 어려운 감이 있다. 내 경우는 Ubuntu 데스크탑에서 text 파일의 기본 프로그램으로도 emacsclient를 실행하는데, nautilus에서 텍스트 파일 더블 클릭할 경우에는 -c 옵션을 줘서 새 창으로 띄우고, 그 외 일반 터미널에서 실행하거나 remote로 접속한 상황에서는 또 -t 옵션을 사용해서 터미널 안에서 띄운다거나 하는 등 상황에 따라 다른 방식으로 띄우고 싶을 때에는 위의 간단한 스크립트에 몇 가지를 추가 할 필요가 있다.

#!/bin/sh
EMACSCLIENT="emacsclient"
SERVER_RUNNING=`$EMACSCLIENT -e "(server-running-p)"`
if [ -z "$SERVER_RUNNING" ]; then
    exec emacs -mm $@
    exit 0
fi

RUNNING_AS=`$EMACSCLIENT -e "(process-get server-process ':as)"`
OPT=
if [ -z "$EMACS" ]; then
    if [ "$RUNNING_AS" = "gui" -a -z "`echo $TERM | grep xterm`" ]; then
        OPT="-c"
    else
        OPT="-t"
    fi
fi

exec $EMACSCLIENT $OPT --alternate-editor="emacs" "$@" 2> /dev/null

emacsclient의 -e 옵션은 해당 elisp expression을 서버에서 evaluation해 주는 역할을 하며 리턴 값을 stdout으로 출력해 주며 nil의 경우는 아무 것도 출력하지 않는다. 위 스크립트를 실행하면 $SERVER_RUNNING에는 서버가 실행하고 있을 경우에는 "t" 값이 (엄밀하게는 non-nil인 아무 문자열이겠지만 "t"가 나오긴 한다) 담겨져 있을테고 이 경우 다음 구문으로, 반대의 경우 빈 문자열일테니 emacs를 maximized 모드로 실행하고 스크립트를 종료한다. 다음의 $RUNNING_AS의 경우는 좀 tricky 한 데, 목적은 GUI인지 CUI인지 판단하기 위한 것으로 구문 자체는 서버 프로세스의 property 중 :as 키의 값을 읽어 오는 것이다. :as는 Emacs에서 기본으로 지원하는 것은 아니며 위의 구문을 정상적으로 실행하기 위해선 설정파일에 서버를 띄우는 Emacs 세션에서 :as property를 설정하도록 해줘야 한다. 이제 위의 서버를 구동하는 부분을 아래와 같이 바꿔보자.

(ignore-errors
  (let ((warning-minimum-level :emergency)) ; a kinda tricky way to suppress warning
    (require 'server)
    (unless (server-running-p) (server-start)) ; start server
    (if (processp server-process)
        (process-put server-process ':as (cond ((daemonp) 'daemon)
                                               ((display-graphic-p) 'gui)
                                               (t 'tty))))))

기본적인 아이디어는 데몬으로 구동되고 있을 경우 daemon, GUI 모드일 땐 gui, 그 외의 CUI 환경에선 tty 값을 서버 프로세스의 property로 지정을 해 놓고 그 값을 통해 쉘 스크립트에서 현재 모드를 판단, 그에 맞춰 창을 하나 더 띄울 것인지 아니면 그냥 해당 터미널에 CUI로 띄울 것인지 결정한다는 것이다. 물론 굳이 process property로 지정하지 않고 global 변수에 값을 담아 놓고 그걸로 판단해도 될 거 같긴 한데 그냥 난 이렇게 짰다. 이게 불만인 사람은 고쳐서 쓰면 되겠지.


Windows 환경에서도 비슷하게 할 수는 있지만 사실 어차피 GUI 모드로만 쓸거라 -a 옵션만으로도 충분해서 간단하게 Visual Basic Script를 짜 놓고 VBS To EXE 프로그램으로 실행 파일로 만들어서 쓴다. 근데 사실 Windows에서는 확장자별로 기본 프로그램 매핑하느라 레지스트리 건드리는 게 더 귀찮아서 그닥 효용성이 높지는 않네. 혹시나 특정한 확장자들을 일괄적으로 기본 프로그램 지정할 수 있는 방법이 있으면 댓글로 공유 좀 부탁합니다.... 굽신굽신

' emx.vbs : starts a emacsclient with no command prompt

Dim emxCmd

Set WshShell = Createobject("WScript.Shell")
Set Args = WScript.Arguments
emxCmd = """emacsclient"" ""-cqa"" ""emacs"""
For index = 0 To (Args.Count - 1)
emxCmd = emxCmd & Chr(32) & Chr(34) & Args(index) & Chr(34)
Next
WshShell.Run emxCmd, 0
Set WshShell = Nothing