首页 > IDE, 中级, 自动补全 > Emacs才是世界上最强大的IDE - 用auto-complete实现自动补全

Emacs才是世界上最强大的IDE - 用auto-complete实现自动补全

2009年11月11日 ahei 发表评论 阅读评论

以前emacs下面一直没有什么比较好用的补全, 一开始有位朋友推荐给我company-mode, 用了一下, 感觉挺慢的(可能当时我用了semantic的补全), 就没用了, 后来, 这位朋友又推荐给我auto-complete, 这个补全是国外一个牛人大概在2008.11.9左右开发出来的, 最新的版本才0.3.0 alpha, 不过我试用了一下就觉得非常好用, 从此就开始用它来补全了, 它有三大优点:

  • 补全是通过弹出菜单来展现的, 比较直观(一般现在的IDE也都是这样实现的), 其实hippie-expand的补全也还行, 但是就是没有弹出菜单, 不够直观
  • 补全的弹出菜单是用overlay实现的, 所以在emacs的字符界面下也能用, 以前也有些补全比如cedet也能弹出补全菜单, 但是只能在GUI界面下使用
  • 它通过各种backend来实现各种mode下的补全的, 比如:补全全路径文件名的backend,补全单独文件名的backend,补全当前buffer下单词的backend,补全所有buffer下的单词的backend,补全Elisp语法的引擎,补全yasnippet片段的引擎,补全缩写的引擎,等等等等,用户也可以自己实现这些backend, 扩展性非常好(扩展性和定制性对一个软件来说是至关重要的, 这方面emacsvim做的很好, 所以才能成为最优秀的软件)。

说了这么多, 大家还不知道auto-complete长啥样呢, 我们先来看看它:

emacs的代码补全

emacs的代码补全

emacs的代码补全

emacs的代码补全

auto-complete作者还给了一段视频来演示它.
auto-complete的配置文件在这里,配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
;; -*- Emacs-Lisp -*-
 
;; Time-stamp: <2010-04-09 10:22:51 Friday by ahei>
 
(require 'auto-complete)
(require 'auto-complete-config)
(require 'auto-complete-yasnippet)
(require 'auto-complete-c)
(require 'auto-complete-etags)
(require 'auto-complete-extension)
(require 'auto-complete-octave)
(require 'auto-complete-verilog)
(require 'auto-complete+)
(require 'util)
 
(defun auto-complete-settings ()
  "Settings for `auto-complete'."
  ;; After do this, isearch any string, M-: (match-data) always
  ;; return the list whose elements is integer
  (global-auto-complete-mode 1)
 
  ;; 不让回车的时候执行`ac-complete', 因为当你输入完一个
  ;; 单词的时候, 很有可能补全菜单还在, 这时候你要回车的话,
  ;; 必须要干掉补全菜单, 很麻烦, 用M-j来执行`ac-complete'
  (apply-define-key
   ac-complete-mode-map
   `(("<return>"   nil)
     ("RET"        nil)
     ("M-j"        ac-complete)
     ("<C-return>" ac-complete)
     ("M-n"        ac-next)
     ("M-p"        ac-previous)))
 
  (setq ac-dwim t)
  (setq ac-candidate-max ac-candidate-menu-height)
 
  (set-default 'ac-sources
               '(ac-source-semantic
                 ac-source-yasnippet
                 ac-source-abbrev
                 ac-source-words-in-buffer
                 ac-source-words-in-all-buffer
                 ac-source-imenu
                 ac-source-files-in-current-dir
                 ac-source-filename))
  (setq ac-modes ac+-modes)
 
  (dolist (command `(backward-delete-char-untabify delete-backward-char))
    (add-to-list 'ac-trigger-commands command))
 
  (defun ac-start-use-sources (sources)
    (interactive)
    (let ((ac-sources sources))
      (call-interactively 'ac-start)))
 
  (defvar ac-trigger-edit-commands
    `(self-insert-command
      delete-backward-char
      backward-delete-char
      backward-delete-char-untabify)
    "*Trigger edit commands that specify whether `auto-complete' should start or not when `ac-completing'."))
 
(eval-after-load "auto-complete"
  '(auto-complete-settings))
 
(eval-after-load "cc-mode"
  '(progn
     (dolist (command `(c-electric-backspace
                        c-electric-backspace-kill))
       (add-to-list 'ac-trigger-commands command)
       (add-to-list 'ac-trigger-edit-commands command))))
 
(eval-after-load "autopair"
  '(progn
     (dolist (command `(autopair-insert-or-skip-quote
                        autopair-backspace
                        autopair-extra-skip-close-maybe))
       (add-to-list 'ac-trigger-commands command))
 
     (defun ac-trigger-command-p ()
       "Return non-nil if `this-command' is a trigger command."
       (or
        (and
         (memq this-command ac-trigger-commands)
         (let* ((autopair-emulation-alist nil)
                (key (this-single-command-keys))
                (beyond-autopair (or (key-binding key)
                                     (key-binding (lookup-key local-function-key-map key)))))
           (memq beyond-autopair ac-trigger-edit-commands)))
        (and ac-completing
             (memq this-command ac-trigger-edit-commands))))))
 
(defun ac-settings-4-lisp ()
  "Auto complete settings for lisp mode."
  (setq ac-omni-completion-sources '(("\\<featurep\s+'" ac+-source-elisp-features)
                                     ("\\<require\s+'"  ac+-source-elisp-features)
                                     ("\\<load\s+\""    ac-source-emacs-lisp-features)))
  (ac+-apply-source-elisp-faces)
  (setq ac-sources
        '(ac-source-yasnippet
          ac-source-symbols
          ;; ac-source-semantic
          ac-source-abbrev
          ac-source-words-in-buffer
          ac-source-words-in-all-buffer
          ;; ac-source-imenu
          ac-source-files-in-current-dir
          ac-source-filename)))
 
(defun ac-settings-4-java ()
  (setq ac-omni-completion-sources (list (cons "\\." '(ac-source-semantic))
                                         (cons "->" '(ac-source-semantic))))
  (setq ac-sources
        '(;;ac-source-semantic
          ac-source-yasnippet
          ac-source-abbrev
          ac-source-words-in-buffer
          ac-source-words-in-all-buffer
          ac-source-files-in-current-dir
          ac-source-filename)))
 
(defun ac-settings-4-c ()
  (setq ac-omni-completion-sources (list (cons "\\." '(ac-source-semantic))
                                         (cons "->" '(ac-source-semantic))))
  (setq ac-sources
        '(ac-source-yasnippet
          ac-source-c-keywords
          ac-source-abbrev
          ac-source-words-in-buffer
          ac-source-words-in-all-buffer
          ac-source-files-in-current-dir
          ac-source-filename)))
 
(defun ac-settings-4-cpp ()
  (setq ac-omni-completion-sources
        (list (cons "\\." '(ac-source-semantic))
              (cons "->" '(ac-source-semantic))))
  (setq ac-sources
        '(ac-source-yasnippet
          ac-source-c++-keywords
          ac-source-abbrev
          ac-source-words-in-buffer
          ac-source-words-in-all-buffer
          ac-source-files-in-current-dir
          ac-source-filename)))
 
(defun ac-settings-4-text ()
  (setq ac-sources
        '(;;ac-source-semantic
          ac-source-yasnippet
          ac-source-abbrev
          ac-source-words-in-buffer
          ac-source-words-in-all-buffer
          ac-source-imenu)))
 
(defun ac-settings-4-eshell ()
  (setq ac-sources
        '(;;ac-source-semantic
          ac-source-yasnippet
          ac-source-abbrev
          ac-source-words-in-buffer
          ac-source-words-in-all-buffer
          ac-source-files-in-current-dir
          ac-source-filename
          ac-source-symbols
          ac-source-imenu)))
 
(defun ac-settings-4-ruby ()
  (require 'rcodetools-settings)
  (setq ac-omni-completion-sources
        (list (cons "\\." '(ac-source-rcodetools))
              (cons "::" '(ac-source-rcodetools)))))
 
(defun ac-settings-4-html ()
  (setq ac-sources
        '(;;ac-source-semantic
          ac-source-yasnippet
          ac-source-abbrev
          ac-source-words-in-buffer
          ac-source-words-in-all-buffer
          ac-source-files-in-current-dir
          ac-source-filename)))
 
(defun ac-settings-4-tcl ()
  (setq ac-sources
        '(;;ac-source-semantic
          ac-source-yasnippet
          ac-source-abbrev
          ac-source-words-in-buffer
          ac-source-words-in-all-buffer
          ac-source-files-in-current-dir
          ac-source-filename)))
 
(defun ac-settings-4-awk ()
  (setq ac-sources
        '(;;ac-source-semantic
          ac-source-yasnippet
          ac-source-abbrev
          ac-source-words-in-buffer
          ac-source-words-in-all-buffer
          ac-source-files-in-current-dir
          ac-source-filename)))
 
(am-add-hooks
 `(lisp-mode-hook emacs-lisp-mode-hook lisp-interaction-mode-hook
                  svn-log-edit-mode-hook change-log-mode-hook)
 'ac-settings-4-lisp)
 
(apply-args-list-to-fun
 (lambda (hook fun)
   (am-add-hooks hook fun))
 `(('java-mode-hook   'ac-settings-4-java)
   ('c-mode-hook      'ac-settings-4-c)
   ('c++-mode-hook    'ac-settings-4-cpp)
   ('text-mode-hook   'ac-settings-4-text)
   ('eshell-mode-hook 'ac-settings-4-eshell)
   ('ruby-mode-hook   'ac-settings-4-ruby)
   ('html-mode-hook   'ac-settings-4-html)
   ('java-mode-hook   'ac-settings-4-java)
   ('awk-mode-hook    'ac-settings-4-awk)
   ('tcl-mode-hook    'ac-settings-4-tcl)))
 
(eal-eval-by-modes
 ac-modes
 (lambda (mode)
   (let ((mode-name (symbol-name mode)))
     (when (and (intern-soft mode-name) (intern-soft (concat mode-name "-map")))
       (define-key (symbol-value (am-intern mode-name "-map")) (kbd "C-c a") 'ac-start)))))
 
(provide 'auto-complete-settings)

auto-complete的使用很简单, 你在输入的时候, 它会弹出一个菜单提示你有哪些补全, 你可以通过M-n和M-p来上下选择, 然后按回车或者M-j来确定你的选择, 或者按TAB键来选择补全, 这样就不用再按回车键或者M-j来确认选择了, 按其它没有绑定到ac-complete-mode-map上的键, 会自动关闭auto-complete的补全菜单, 比其他的一些IDE下的补全用起来方便多了.

auto-complete默认的文件全路径名补全和当前路径下的文件名(不包括路径)补全会提示出一些你不需要的文件, 比如当前目录”.”和父目录”.”以及.svn,CVS, 还有在emacs-lisp-mode-map下补全symbol的时候, 会提示一些垃圾symbol, 我写了一个auto-complete+(AutoCompletePlus)来改正这些缺点.

最近水木的emacs版上, 一位网友提出auto-complete不能在c++-mode下用ac-source-filename, ac-source-filename是补全全路径名的, 比如你现在输入/ho, auto-complete就会提示/home/, 然后你选择/home/, auto-complete又会提示/home/ahei/, 这样你又可以选择它, 总之, 对输入全路径名非常方便.我试了一下果然如此, 最后通过看auto-complete源码才知道原来这是auto-complete的一个bug.我们下面来看一下.auto-complete的开始补全是通过命令ac-start的执行的, ac-start的定义如下:

1
2
3
4
5
6
7
8
9
(defun ac-start ()
  "Start completion."
  (interactive)
  (let* ((point (save-excursion (funcall ac-prefix-function)))
         (reposition (or (not (equal ac-point point))
                         ;; If menu direction is positive and next visual line belongs
                         ;; to same buffer line, then need reposition
                         (and (> ac-menu-direction 0)
                              (ac-menu-at-wrapped-line)))))

auto-complete在补全时通过ac-prefix-function这个函数来确定要待补全的前缀ac-prefix, ac-prefix-function的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
(defvar ac-prefix-function 'ac-sources-prefix
  "When `auto-complete-mode' finds it can start completion
or update candidates, it will call this function to find a
start point of the prefix.
 
If this function returns a point `auto-complete-mode'
will set the substring between the point and current point to `ac-prefix'.
And also it will start completion or update candidates by using
the `ac-prefix'.
 
If this function returns `nil', `auto-complete-mode'
ignore starting completion or stop completing.")

它的值是ac-sources-prefix, 我们再来看看它的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(defun ac-sources-prefix ()
  "Implemention for `ac-prefix-function' by sources."
  (let (point)
    (dolist (pair ac-omni-completion-sources)
      (when (looking-back (car pair) nil t)
        (setq ac-current-sources (cdr pair))
        (setq ac-sources-omni-completion t)
        (setq ac-completing t)
        (setq point (match-end 0))))
    (or point
        (if (and ac-completing ac-sources-omni-completion)
            ac-point
          (setq ac-current-sources ac-sources)
          (setq ac-sources-omni-completion nil)
          (funcall ac-sources-prefix-function)))))

这个函数最终调用的是ac-sources-prefix-function, 其定义如下:

1
2
3
(defvar ac-sources-prefix-function 'ac-sources-prefix-default
  "Default prefix function for sources.
You should override this variable instead of ac-prefix-function.")

看来它的值是ac-sources-prefix-default, 而ac-sources-prefix-default是:

1
2
3
4
(defun ac-sources-prefix-default ()
  "Default implementation for `ac-sources-prefix-function'."
  (require 'thingatpt)
  (car-safe (bounds-of-thing-at-point 'symbol)))

好了, 我们现在已经接近真相了, 原来auto-complete是通过调用bounds-of-thing-at-point来查找当前光标下的一个symbol的边界, 然后取得这个边界的第一个元素, 即边界的开始处, 然后把这个边界的开始处到当前光标下的内容作为ac-prefix.那么我们现在只要来看一下c++-mode下的当前光标下的symbol是什么就知道为什么不能补全全路径名了.

我们先来打开一个cpp文件, 然后输入/home/ahe, 光标停在i后面, 这时候执行M-x eval-expression, 然后输入(thing-at-point ‘symbol), emacs提示:

  #("ahe" 0 3 (selection-face nil candidate-face nil action nil fontified t face font-lock-string-face))

这时候你应该已经知道了吧, 在c++-mode下当前光标的symbol是ahe, 并不是/home/ahe, 不能补全/home/ahei/理所当然.

那我们怎样来修改这个bug呢?我觉得每个mode下应该有自己的ac-sources-prefix-function, 而且不能只取得一个thing, 比如symbol, 应该能取得多个things, 比如symbol和filename, 然后提示以它们作为前缀的所有补全.可惜这个比较难修改, 只能留待作者去改进了, 我已经给作者提交了bug report.

后记

也许有人使用过yasnippet,觉得yasnippet比auto complete好用多了,关于这个,请看auto complete和yasnippet的区别

分享家:Addthis中国
GD Star Rating
loading...
Emacs才是世界上最强大的IDE - 用auto-complete实现自动补全, 8.5 out of 10 based on 15 ratings 标签:ahei, auto complete plus, buffer, C/C++, CEDET, company-mode, DEA, ede, Elisp, Emacs, eval-after-load, face, hippie-expand, IDE, imenu, java, lambda, lightbox, lisp, mode, org, screenshot, se, semantic, snippet, text, top, vi, vim, yasnippet, 代码补全, 光标, 定制, 开发, 扩展, 按键, 自动完成, 自动提示, 自动补全, 补全, 补齐, 配置, 配置文件

相关日志

分类: IDE, 中级, 自动补全
  1. bigclean
    2009年11月13日12:12 | #1

    还是比较喜欢company-mode 的补全,感觉速度也还行。

    [回复]

    ahei 回复:

    呵呵,主要是当时用了semantic,导致比较慢,我准备等有空的时候再试试company mode。不过据auto-complete作者说company-mode bug有点多,:)

    CompletionUI use tooltip as completion UI, AutoComplete and CompanyMode implement by Overlay.
    Tooltip just can use in X, and overlay can use both X and console.
    Tooltip is slower than overlay when finger is very fast. :)
    But overlay have align render bug with multi-byte language (such as CJK), and tooltip haven’t this problem.
    About different between AutoComplete and CompanyMode, in fact they’re very similar, but CompanyMode have more bug. – AndyStewart

    see http://www.emacswiki.org/emacs/AutoComplete#toc9

    [回复]

  2. bigclean
    2009年11月13日22:56 | #2

    自己对自动补全的要求也不是特别高,只是写程序时,比较需要,感觉配合semantic自动补全成员变量函数就ok了,与global(gtags)的配合还在尝试中。感觉auto-compeletion更通用,compeletionui倒是没有配置过。

    [回复]

    ahei 回复:

    我还是比较喜欢自动补全的,可惜Emacs的语法补全一直不咋的, 实在搞不懂为啥那么多Emacs大牛就搞不定一个语法补全,:)

    [回复]

  3. bigclean
    2009年11月14日00:31 | #3

    可能是哲学理念的缘故,技术上应该是没有问题的。emacs连显示行号,tab的支持越需要插件的支持。还是要提一下vim,vim的自动补全(omni-compelete),tab的支持倒是好的多。不知博主是否排斥vim?呵呵。 :)
    编辑一些linux的配置文件或是一些少见的程序,还是会用用vim。

    [回复]

    ahei 回复:

    技术上我也相信那么多emacs大牛啊, 不过现有的语法补全确实不咋的, 像cedet,ecb,xref,global都不太好用, linux下的source navigator还不错, 对c++支持的比较好, 不过另人很晕的是, 它竟然不支持局部变量.
    vim我不怎么熟悉,只知道一些比较基本的使用, 总是觉得Vim的快捷键太难按了(虽然短, 但是离键盘区太远, 设计的远没有emacs合理,
    这点也是我非常欣赏emacs的原因), 而且Vim没有mode也很不爽, 还有, 编辑时总要一会切换到命令模式,一会切换到编辑模式, 太烦了,
    还是emacs好, 不知道阁下对Vim熟悉不?对我说的模式切换的问题有没有啥好的解决方法?对了, vim的补全怎么样?我估计语法方面也不
    咋的吧

    [回复]

    babyking1949 回复:

    目前vi切换模式的方法我是把双击capslock键映射为esc键 了,所以切换起来毫无压力

    [回复]

  4. bigclean
    2009年11月14日01:19 | #4

    vim的mode的确让很多人困惑,自己也是,模式是vim的象征了。关于你的模式的问题,自己也还是没有方法。更麻烦的输入中文,在vim邮件列表倒是看到了针对linux输入法的tricks。
    vim的omni-complete是vim7新增的特性,自动补全功能已经很不错了,非常的强大,可以依据当前文件也可以依据字典文件或是头文件,但是涉及到的快捷键还是比较繁琐的,自动弹出自动补全窗口是需要插件支持(vim-autocomplpop)。
    自己感觉vim的插件系统比较不如emacs,vim的脚本语言还是不如lisp来的有诱惑。

    [回复]

    ahei 回复:

    vim的语法补全功能怎么样呢? :) vim的插件系统那可是公认的没有Emacs强大, Emacs一开始就比较关注向扩展性,定制性,平台性方向发展,
    vim则只是专注于做一个editor, 所以它默认的就比较好用, 不需要怎么配置的, 一些该有的功能都有, 比如行号,
    tab

    [回复]

  5. 2009年11月25日21:29 | #5

    auto complete,配置了好久都没弄好,不知道是不是不支持semantic补全?(ctage那些都不会用)semantic慢但配合company很好配置。

    [回复]

    ahei 回复:

    你看我的配置文件啊,支持semantic补全的
    semantic的cvs版本不慢,所以我最近正在学cedet,等弄好了再写文章来介绍

    [回复]

  6. transtone
    2009年11月27日07:24 | #6

    俺是找color-theme找到这里来的。
    自动补全还是 yasnippet 方便,是浙大的一个高手在本科期间开发的。vim下面有个 snipmate,功能差不多。
    您可以先看看这个:http://yasnippet.googlecode.com/files/yas_demo.avi

    [回复]

  7. transtone
    2009年11月27日09:23 | #7

    @transtone
    仔细看了兄弟的配置,似乎您已经用过yasnippet,能在文章中比较一下 autoComplete和yasnippet的使用感受吗?

    [回复]

    ahei 回复:

    呵呵,你可以看看auto complete和yasnippet的区别

    [回复]

  8. meteor1113
    2009年12月1日23:15 | #8

    [Comment ID #10562 Will Be Quoted Here]

    关于auto-complete作者说company-mode那段话应该是很早的事了,那时候company-mode应该还在0.3以下,后来的版本已经修正了很多了。虽然现在也还有些bug,不过比起当初已经强了不是一点两点了,新版本用起来还是很舒服的

    [回复]

    ahei 回复:

    可能是吧,我也查不出来作者的那段话是什么时候说的了,emacswiki上只能列出最近几个版本的差异,我有空会去试试company mode的。:)

    [回复]

  9. transtone
    2010年1月26日13:11 | #9

    一直关注auto-complete,但看博主的设置与改动实在有点多,即便上个月在emacs-overlay库中新加入auto-complete的ebuild,也没敢贸然下手。今天忍不住折腾了一下,原来用起来也很简单,有一点小问题在emacswiki中都找到了答案。我比较懒,没有用博主的修改版与设置,用的是gentoo emacs-overlay库中的版本: http://github.com/m2ym/auto-complete/ 。有用gentoo并追新的朋友碰巧搜到这里,也可以参考一下我修改的ebuild: http://github.com/transtone/transconfig/tree/master/zm-overlay/app-emacs/auto-complete/
    多谢博主的推荐。

    [回复]

    ahei 回复:

    呵呵,默认的就可以用了,我所增加的配置只是满足我那20%的需求而已,千万不要因为我配置的多就不敢用了,自己去看看它文件中的说明就可以了,一般简单配置一下就可以了.

    [回复]

  10. asdf
    2010年3月29日18:00 | #10

    可以看看 msf-abbrev
    看介绍听强大滴
    一个gif动画
    http://www.bloomington.in.us/~brutt/msf-abbrev-demo.gif
    主页
    http://www.bloomington.in.us/~brutt/msf-abbrev.html

    [回复]

    ahei 回复:

    @asdf, 这个与auto-complete是不同种类的插件,msf和yasnippet性质差不多,是做片段扩展的,不过msf-abbrev远没有yasnippet强大。你可以看看auto complete和yasnippet的区别.auto-complete可以利用msf-abbrev的abbrev。我现在的auto-complete配置就用了abbrev.

    [回复]

评论分页
1 2 3 ... 6 34140
  1. 2010年9月19日22:22 | #1
  2. 2010年9月27日16:51 | #2
:wink: :-| :-x :twisted: :) 8-O :( :roll: :-P :oops: :-o :mrgreen: :lol: :idea: :-D :evil: :cry: 8) :arrow: :-? :?: :!: