ㅈㅅㄹ

Auto-complete 패키지에는 여러가지 backend를 통해 자동 완성을 위한 dictionary를 추가할 수 있고, C 헤더 파일명의 자동 완성을 위해서는 auto-complete-c-headers 패키지를 사용할 수 있는데, 문제는 이게 표준 include 디렉토리인 /usr/include, /usr/local/include 만을 포함하고 있어서 컴파일러 전용 헤더 파일이나 C++ 헤더 같은 것들도 자동 완성이 되지 않을 뿐더러, 추가 라이브러리의 경우도 표준 디렉토리에서의 상대 경로를 모두 적어줘야만 하는 불편함이 있다. 물론 패키지 디렉토리를 추가로 적어줘서 #include <GLES/gl2.h> 처럼 적어 주는 건 좋은 습관이긴 하나, 더러운 gnome 시리즈 라이브러리의 경우는 패키지명-버전번호 > 패키지명 > 실제 헤더 파일의 디렉토리 구성을 가지고 있지만, pkg-config --cflags pango을 사용하더라도 -I/usr/include/pango-1.0를 던져주므로, 일반적으로 #include <pango/pango.h> 처럼 사용하는 식인데, auto-complete-c-headers의 경우 기본적으로는 이를 잘 지원하지 못한다. 물론 Qt 라이브러리의 경우도 마찬가지고.


물론 achead:include-directories라는 list에 헤더파일이 있는 경로를 추가 하면 그 경로를 탐색하도록 auto-complete-c-headers에서 이미 인터페이스는 만들어 놓았지만, 문제는 이걸 일일히 손수 추가해주느냐 혹은 알아서 자동으로 추가하도록 할 것이냐인데. 내 경우는 동일한 설정을 GitHub에 올려두고 여러 곳에서 사용하기 때문에 항상 그 설정이 잘 동작하리라는 보장도 없거니와, 가급적 자동화하지 않으면 안되는 고약한 성질머리 때문에 아래와 같은 설정을 추가하여 헤더 경로를 설정하도록 했다.

;;; utils-auto-complete.el

;; Written by Yunsik Jang <doomsday@kldp.org>
;; You can use/modify/redistribute this freely.

(defconst my:achead-std-begin-exp "^#include .+ starts here:$")
(defconst my:achead-std-end-exp "^End of search list.$")
(defvar my:achead-std-begin-exp-found nil)

(defun my:achead-find-qt-headers ()
  (if (executable-find "qmake")           ; qt headers
      (split-string (shell-command-to-string "qmake -query QT_INSTALL_HEADERS"))))

;; grap some include paths from compiler
(defun my:achead-find-std-headers (lang)
  (let ((buffer-name "*my:achead-find-std*")
        (compiler (or (getenv "CC")
                      (executable-find "gcc")
                      (and (interactive-p) (error "No compiler found!")))))
    (when (and compiler
               (= 0 (call-process compiler
                                  nil buffer-name nil
                                  (concat "-x" (downcase (or (and (stringp lang) lang)
                                                             (signal 'wrong-type-argument (list lang)))))
                                  "-E" "-v" "-")))
      (with-current-buffer (get-buffer buffer-name)
        (let ((lines (split-string (buffer-string) "\n"))
              result
              skip-line)
          (save-excursion
            (goto-char 0)
            (dolist (line lines)
              (setq skip-line nil)
              (if (string-match my:achead-std-begin-exp line)
                  (progn (setq my:achead-std-begin-exp-found t)
                         (setq skip-line t))
                (if (string-match my:achead-std-end-exp line)
                    (setq skip-line t))
                (if (and (not skip-line) my:achead-std-begin-exp-found)
                    (dolist (w (split-string line))
                      (if (and (> (length w) 0) (string-prefix-p "/" w))
                          (push w result)))))))
          (set-buffer-modified-p nil)
          (kill-buffer (current-buffer))
          result)))))

;; include subdirectories like gtk+-2.0 or QtCore for auto-complete-c-headers
(defconst my:achead-subdir-regexp "[A-Za-z0-9-_]+\\-[0-9.]+\\|Qt[A-Za-z]+")
(defun my:achead-find-subdirs (search-paths)
  (let* (includedirs)
    (dolist (d search-paths)
      (and (file-directory-p d)
           (string-prefix-p "/" d)
           (dolist (subd (directory-files d nil my:achead-subdir-regexp))
             (and (file-directory-p (expand-file-name subd d))
                  (push (expand-file-name subd d) includedirs)))))
    includedirs))

(eval-after-load 'auto-complete-c-headers
  '(let ((paths (append (my:achead-find-std-headers "c")
                        (my:achead-find-std-headers "c++")
                        (my:achead-find-qt-headers))))
     (dolist (path (append paths (my:achead-find-subdirs paths)))
       (add-to-list 'achead:include-directories path))))

(provide 'utils-auto-complete)

구조는 단순하다. 우선 my:achead-find-qt-headers은 Qt 헤더를 추가하기 위한 것으로, qmake가 시스템에 있다면 qmake -query QT_INSTALL_HEADERS을 통해 디렉토리 list를 가져 와서 리턴한다. my:achead-find-std-headers는 컴파일러에서 주어진 언어별로 참조하는 include 디렉토리 리스트를 가져오는 것으로 현재로썬 GCC에서만 동작하도록 되어 있지만, 어쨌건 궁극적으로는 C++의 경우 gcc -x c++ -E -v - 명령어를 실행해서 include 경로만 긁어 와서 리턴하도록 하는 용도이다. 해당 명령어를 실행하면 아래와 같은 결과가 나오는데,

Using built-in specs.
COLLECT_GCC=gcc
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 4.8.4-2ubuntu1~14.04.1' --with-bugurl=file:///usr/share/doc/gcc-4.8/README.Bugs --enable-languages=c,c++,java,go,d,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.8 --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.8 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --enable-gnu-unique-object --disable-libmudflap --enable-plugin --with-system-zlib --disable-browser-plugin --enable-java-awt=gtk --enable-gtk-cairo --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-4.8-amd64/jre --enable-java-home --with-jvm-root-dir=/usr/lib/jvm/java-1.5.0-gcj-4.8-amd64 --with-jvm-jar-dir=/usr/lib/jvm-exports/java-1.5.0-gcj-4.8-amd64 --with-arch-directory=amd64 --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --enable-objc-gc --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 4.8.4 (Ubuntu 4.8.4-2ubuntu1~14.04.1) 
COLLECT_GCC_OPTIONS='-E' '-v' '-mtune=generic' '-march=x86-64'
 /usr/lib/gcc/x86_64-linux-gnu/4.8/cc1plus -E -quiet -v -imultiarch x86_64-linux-gnu -D_GNU_SOURCE - -mtune=generic -march=x86-64 -fstack-protector -Wformat -Wformat-security
ignoring duplicate directory "/usr/include/x86_64-linux-gnu/c++/4.8"
ignoring nonexistent directory "/usr/local/include/x86_64-linux-gnu"
ignoring nonexistent directory "/usr/local/include"
ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../x86_64-linux-gnu/include"
#include "..." search starts here:
#include <...> search starts here:
 /usr/include/c++/4.8
 /usr/include/x86_64-linux-gnu/c++/4.8
 /usr/include/c++/4.8/backward
 /usr/lib/gcc/x86_64-linux-gnu/4.8/include
 /usr/lib/gcc/x86_64-linux-gnu/4.8/include-fixed
 /usr/include/x86_64-linux-gnu
 /usr/include
End of search list.
# 1 "<stdin>"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<command-line>" 2
# 1 "<stdin>"
COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/4.8/:/usr/lib/gcc/x86_64-linux-gnu/4.8/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/4.8/:/usr/lib/gcc/x86_64-linux-gnu/
LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/4.8/:/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../:/lib/:/usr/lib/
COLLECT_GCC_OPTIONS='-E' '-v' '-mtune=generic' '-march=x86-64'

이 결과를 간단하게 파싱해서 아래의 list를 뽑아 낸다.

("/usr/include" "/usr/include/x86_64-linux-gnu"
 "/usr/lib/gcc/x86_64-linux-gnu/4.8/include-fixed"
 "/usr/lib/gcc/x86_64-linux-gnu/4.8/include"
 "/usr/include/c++/4.8/backward"
 "/usr/include/x86_64-linux-gnu/c++/4.8"
 "/usr/include/c++/4.8" "/usr/lib/gcc/x86_64-linux-gnu/4.8/cc1plus")

그리고 나머지 my:achead-find-subdirs 함수는 현재 주어진 경로들의 하위 디렉토리 중 my:achead-subdir-regexp에 매칭되는 이름, 즉 pango-1.0 이라든가 QtCore 같은 디렉토리들의 list 를 리턴해 주어서 gnome 시리즈 헤더 파일의 자동 완성을 #include <pango/pango.h> 처럼 시켜 주고, Qt의 경우 #include <QtCore/QString>처럼 사용하는 대신, 그냥 #include <QString>처럼 사용할 수 있도록 한다. 사실 Qt의 경우는 취향의 문제인 것 같은데 원치 않으면 my:achead-subdir-regexp"[A-Za-z0-9-_]+\\-[0-9.]+" 처럼 사용하면 되겠다.



이제 함수들을 선언했으니 실제 함수들을 호출해서 achead:include-directories에 디렉토리 list를 추가해야 할텐데, 적절한 호출 시점은 auto-complete-c-headers가 로드된 후이므로 eval-after-load을 사용하여 해당 라이브러리 로드 후에 디렉토리를 업데이트 하도록 한다. 이맥스를 재 시작 후 잘 동작하는 지 확인해 보자. 이제 C++ 헤더는 물론이고 위 사진 처럼 gnome 시리즈들도 적절하게 자동 완성이 되는 걸 볼 수 있다.

'Emacs' 카테고리의 다른 글

Emacs spell checker  (0) 2016.03.11
Emacs의 ansi color 설정  (0) 2016.03.07
자동 byte-compile 설정  (0) 2016.03.03
Emacsclient의 활용  (0) 2016.03.02
Trailing white spaces 표시 하기  (0) 2016.03.02