首页 > 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. 2017年7月19日20:10 | #1

    Some genuinely choice content on this internet site,
    saved to fav.

    [回复]

评论分页
1 ... 5 6 7 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: :-? :?: :!: