首页 > 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.2 out of 10 based on 12 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. 悟空
    2010年6月29日05:10 | #1

    请问如何打开某种文件(比如R代码文件)时自动激活呢?

    [回复]

    ahei 回复:

    @悟空, 这个得自己写elisp了

    [回复]

    悟空 回复:

    @ahei, 我想如果能做到2件事就可以了: 1, 判断mode并响应; 2, 激活auto-complete-mode. 只是不知道用什么函数啊. 8-O

    [回复]

    ahei 回复:

    @悟空, major-mode (auto-complete-mode 1)

    [回复]

    匿名 回复:

    @ahei, (auto-complete-mode 1)似乎也不行…

    [回复]

    悟空 回复:

    @ahei, 还有那个global-auto-complete-mode激活后, 我以为真的到哪都激活了呢.

    [回复]

    ahei 回复:

    @悟空,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    (define-global-minor-mode global-auto-complete-mode
      auto-complete-mode auto-complete-mode-maybe
      :group 'auto-complete)
     
    (defun auto-complete-mode-maybe ()
      "What buffer `auto-complete-mode' prefers."
      (if (and (not (minibufferp (current-buffer)))
               (memq major-mode ac-modes))
          (auto-complete-mode 1)))

    [回复]

    匿名 回复:

    @悟空, 我做到了: (add-hook ‘R-mode ‘ac-start) :-D

    [回复]

    匿名 回复:

    @悟空, 再啰嗦一句: 我用tab键来完成补全.

    [回复]

  2. Noe
    2010年8月24日14:19 | #2

    想问一下autocomplete如何设置使用gtags的backend呢?我自己试了不管用。

    [回复]

    ahei 回复:

    @Noe, 工作忙,有空帮你看看,你也可以去水木上问问

    [回复]

  3. 阅读中
    2010年9月17日21:01 | #3

    @阅读中
    (global-auto-complete-mode 1) 我用这行后启动后自动打开auto-complete模式,但是我用M-x换到另一个模式它又关闭了,而不像yasnippet一直是开着的.

    你上新浪微博吗?如不建议我在微博上向你提问,请告诉我微博帐号吧!

    [回复]

    ahei 回复:

    @阅读中, 那个mode没有加到ac-modes吧?

    [回复]

    ahei 回复:

    @阅读中, 可以看”关于”页面联系我, :)

    [回复]

    阅读中 回复:

    @ahei,
    那我还是直接在这问你好了,这样别人也可看到答案。我还有一问:
    我想在开着xml-mode的时候也开着actionscript-mode。因为我用mxml要在xml里插入
    actionscript代码。不知是否有更好方法支持二者混合的代码。
    学emacs的中文书籍太少了,几乎就是没有。elisp的更少了。我于像这种英文差的人学起来真的很困难。
    有想过写一本吗?我一定买。

    [回复]

    ahei 回复:

    @阅读中, 可以用mmm-mode或者two-mode-mode. 不是有本学习GNU Emacs吗?可以看看那个,那个还可以啊.呵呵,以我现在的能力,顶多能写一本Emacs使用和简单elisp的书, 而且像Emacs的书,买的人肯定很少的,现在精力很有限,还没有打算出这方面的书.谢谢你的支持. :)

    [回复]

  4. 阅读中
    2010年9月19日13:40 | #4

    我安装了mmm-mode 要使用的代码入下,不知道如何配置。求解。

    [回复]

    ahei 回复:

    …..没用过,你自己研究吧

    [回复]

  5. 阅读中
    2010年9月20日20:35 | #5

    auto-complete可以不按tab键自动补全括号的另一边吗?

    [回复]

    ahei 回复:

    @阅读中, 自动补全括号的另一边?啥意思?

    [回复]

    阅读中 回复:

    @ahei,
    我搞定了,但不是用auto-complete
    配置代码如下:

    ;; (setq skeleton-pair t)
    (local-set-key (kbd “[“) ’skeleton-pair-insert-maybe)
    (local-set-key (kbd “(“) ’skeleton-pair-insert-maybe)
    (local-set-key (kbd “{“) ’skeleton-pair-insert-maybe)
    (local-set-key (kbd “<") 'skeleton-pair-insert-maybe)
    (local-set-key (kbd "'") 'skeleton-pair-insert-maybe)
    (local-set-key (kbd "\"") 'skeleton-pair-insert-maybe)

    [回复]

    ahei 回复:

    @阅读中, 哦,你这个意思啊,那用autopair是最合适的了

    [回复]

  6. niejieqiang
    2010年11月2日19:03 | #6

    这个和yasnippte相比哪个强些? :?:

    [回复]

    ahei 回复:

    @niejieqiang, 不是一个东西, 请看 http://emacser.com/auto-complete_yasnippet.htm

    [回复]

  7. weikent
    2010年12月10日14:38 | #7

    请问你的auto-complete-setting.el有2个版本 一个是
    另一个是
    我2个都试了试 ,里面都有 “Symbol’s function definition is void: am-add-hooks“

    版本里
    (add-to-list ‘ac-dictionary-directories (concat my-emacs-lisps-path “auto-complete/dict”))
    这句会报错。。my-emacs-lisps-path 找不到。。

    请问能解决这个问题么。。。

    [回复]

    ahei 回复:

    @weikent, 下载DEA,在DEA里面搜索am-add-hooks

    [回复]

  8. YDLX
    2011年1月9日20:37 | #8

    请问,里面auto-complete-c和util的包是哪一个?找半天没找到

    [回复]

    ahei 回复:

    @YDLX, find and grep in DEA

    [回复]

    YDLX 回复:

    @ahei, 请问,DEA是什么?

    [回复]

    ahei 回复:

    @YDLX, google ahei dea

    [回复]

    YDLX 回复:

    @ahei, 多谢,好久没用了。现在emacs进步真快啊~

    [回复]

  9. heavegoast
    2011年3月26日17:55 | #9

    想请教一下,为什么那个下拉菜单不能把所有的补全符号都列出来呢?
    另外,我发现下位菜单出现的同时按那个M-/的时候有另外一个补全,这个补全提示有全部的信息,但是一次只显示一条。

    [回复]

    ahei 回复:

    @heavegoast, 因为太多了阿,有限制的,M-/和那个执行的不是同一个命令

    [回复]

    heavegoast 回复:

    @ahei,

    那怎么样把这个限制去掉,譬如弄成一个有滚动条的补全,嘿嘿。

    [回复]

    ahei 回复:

    @heavegoast, 滚动条不行,但是你可以弄多点,(setq ac-candidate-max 20)

    [回复]

    heavegoast 回复:

    @ahei, 好的,谢谢。
    我还有发现有一个问题,就是那个下拉菜单,有时会不见了。这时只剩下那个M-/ 起作用了。
    是不是下拉菜单跟某些配置发生了冲动了呢?

    [回复]

    ahei 回复:

    @heavegoast, M-x auto-complete-mode

    [回复]

    heavegoast 回复:

    @ahei, 好,谢谢,也要坚持用起来。

    [回复]

  10. werther
    2011年3月26日19:35 | #10

    请教一下,在C/CPP下,为什么我的类成员补全只有输入 “. 或 “-> 才会出来,而直接输 . 或 -> 不出补全菜单呢?在auto-complete.el,auto-complete+.el 和 auto-complete-setting.el都没有找到相关的函数。

    [回复]

    ahei 回复:

    @werther, 不懂你的意思

    [回复]

    werther 回复:

    @ahei,
    下面的情况:类对象qPushButton
    qPushButton-> //不出补全菜单
    qPushButton”-> //可以出补全菜单,神奇地多了个双引号……

    [回复]

    ahei 回复:

    @werther, 你哪配置的问题吧

    [回复]

    werther 回复:

    @ahei,
    无意中发现一个解决方法。开启
    global-semantic-highlight-func-mode
    global-semantic-decoration-mode
    Global-Semantic-Idle-Summary mode
    类成员补全就能出了。并且可以根据当前位置所需的参数类型出补全菜单。
    不太明白原因……

    [回复]

评论分页
  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: :-? :?: :!: